diff --git a/doc/api/errors.md b/doc/api/errors.md index cc7c668267fd6b..c8fdd30c9445a9 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -3132,6 +3132,13 @@ Failed to set PSK identity hint. Hint may be too long. An attempt was made to renegotiate TLS on a socket instance with renegotiation disabled. + + +### `ERR_TLS_RENEGOTIATION_UNSUPPORTED` + +An attempt was made to renegotiate TLS, but the TLS implementation does not +support caller-initiated renegotiation. + ### `ERR_TLS_REQUIRED_SERVER_NAME` diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 1d31e2b43dc2bd..a93bb57e6cf0ad 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -1843,6 +1843,8 @@ E('ERR_TLS_PROTOCOL_VERSION_CONFLICT', 'TLS protocol version %j conflicts with secureProtocol %j', TypeError); E('ERR_TLS_RENEGOTIATION_DISABLED', 'TLS session renegotiation disabled for this socket', Error); +E('ERR_TLS_RENEGOTIATION_UNSUPPORTED', + 'TLS session renegotiation is unsupported by this TLS implementation', Error); // This should probably be a `TypeError`. E('ERR_TLS_REQUIRED_SERVER_NAME', diff --git a/lib/internal/tls/wrap.js b/lib/internal/tls/wrap.js index d89e501432968a..05ce6955ed9217 100644 --- a/lib/internal/tls/wrap.js +++ b/lib/internal/tls/wrap.js @@ -72,6 +72,7 @@ const { ERR_TLS_INVALID_CONTEXT, ERR_TLS_INVALID_STATE, ERR_TLS_RENEGOTIATION_DISABLED, + ERR_TLS_RENEGOTIATION_UNSUPPORTED, ERR_TLS_REQUIRED_SERVER_NAME, ERR_TLS_SESSION_ATTACK, ERR_TLS_SNI_FROM_SERVER, @@ -1014,8 +1015,13 @@ TLSSocket.prototype.renegotiate = function(options, callback) { try { this._handle.renegotiate(); } catch (err) { + const isBoringSSLRenegotiationUnsupported = + process.features.openssl_is_boringssl && + err?.code === 'ERR_SSL_FUNCTION_SHOULD_NOT_HAVE_BEEN_CALLED'; + const error = isBoringSSLRenegotiationUnsupported ? + new ERR_TLS_RENEGOTIATION_UNSUPPORTED() : err; if (callback) { - process.nextTick(callback, err); + process.nextTick(callback, error); } return false; } diff --git a/test/addons/openssl-get-ssl-ctx/binding.cc b/test/addons/openssl-get-ssl-ctx/binding.cc index 3945ec870fb8b9..47468ffbcd789d 100644 --- a/test/addons/openssl-get-ssl-ctx/binding.cc +++ b/test/addons/openssl-get-ssl-ctx/binding.cc @@ -18,12 +18,12 @@ void GetSSLCtx(const v8::FunctionCallbackInfo& args) { return; } - // Verify the pointer is a valid SSL_CTX by calling an OpenSSL function. - const SSL_METHOD* method = SSL_CTX_get_ssl_method(ctx); - if (method == nullptr) { + // Verify the pointer is a valid SSL_CTX by calling a function available + // across OpenSSL-compatible TLS backends and checking context-owned state. + STACK_OF(SSL_CIPHER)* ciphers = SSL_CTX_get_ciphers(ctx); + if (ciphers == nullptr) { isolate->ThrowException(v8::Exception::Error( - v8::String::NewFromUtf8(isolate, - "SSL_CTX_get_ssl_method returned nullptr") + v8::String::NewFromUtf8(isolate, "SSL_CTX_get_ciphers returned nullptr") .ToLocalChecked())); return; } diff --git a/test/common/boringssl.js b/test/common/boringssl.js new file mode 100644 index 00000000000000..e6e91387c304c7 --- /dev/null +++ b/test/common/boringssl.js @@ -0,0 +1,346 @@ +/* eslint-disable node-core/crypto-check */ + +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const fixtures = require('../common/fixtures'); +const tls = require('tls'); + +// This module is for BoringSSL-specific branches in tests whose original +// OpenSSL coverage cannot run unchanged. Each helper should assert the +// observable BoringSSL behavior that explains why the OpenSSL-specific +// assertions are bypassed. + +/** + * BoringSSL exposes many removed or disabled TLS cipher suites as "no match" + * at secure-context creation time. This is used for suites such as + * finite-field DHE and anonymous ECDH that OpenSSL builds may still negotiate + * in tests. + * @param {Function} fn + */ +function assertNoCipherMatch(fn) { + assert.throws(fn, { + code: 'ERR_SSL_NO_CIPHER_MATCH', + library: 'SSL routines', + function: 'OPENSSL_internal', + reason: 'NO_CIPHER_MATCH', + }); +} + +/** + * BoringSSL does not parse OpenSSL cipher-string commands such as `@SECLEVEL`. + * Those are OpenSSL policy directives, not cipher names. + * @param {Function} fn + */ +function assertInvalidCommand(fn) { + assert.throws(fn, { + code: 'ERR_SSL_INVALID_COMMAND', + library: 'SSL routines', + function: 'OPENSSL_internal', + reason: 'INVALID_COMMAND', + }); +} + +/** + * Node's DHE tests exercise OpenSSL's finite-field DHE cipher support and DH + * parameter-size policy. BoringSSL does not offer these DHE cipher suites on + * this surface, so creating a server context with a DHE-only cipher list fails + * before a handshake can test DH parameter behavior. + */ +function assertFiniteFieldDheUnsupported() { + assertNoCipherMatch(() => { + tls.createServer({ + key: fixtures.readKey('agent2-key.pem'), + cert: fixtures.readKey('agent2-cert.pem'), + ciphers: 'DHE-RSA-AES128-GCM-SHA256', + }); + }); +} + +/** + * OpenSSL security levels reject small keys by policy and can be adjusted with + * `@SECLEVEL` in the cipher string. BoringSSL does not implement those security + * levels: the small-key server context is accepted, while the OpenSSL-specific + * `@SECLEVEL` command is rejected as invalid cipher-string syntax. + */ +function assertOpenSSLSecurityLevelsUnsupported() { + const options = { + key: fixtures.readKey('agent11-key.pem'), + cert: fixtures.readKey('agent11-cert.pem'), + ciphers: 'DEFAULT', + }; + + tls.createServer(options).close(); + + options.ciphers = 'DEFAULT:@SECLEVEL=0'; + assertInvalidCommand(() => tls.createServer(options)); +} + +/** + * Node's multi-key tests rely on OpenSSL accepting an array of private keys and + * matching them with an array of certificates. BoringSSL rejects this mixed + * EC/RSA identity configuration while configuring the certificate chain, before + * a client can negotiate either identity. + */ +function assertMultiKeyUnsupported() { + assert.throws(() => { + tls.createServer({ + key: [ + fixtures.readKey('ec10-key.pem'), + fixtures.readKey('agent1-key.pem'), + ], + cert: [ + fixtures.readKey('agent1-cert.pem'), + fixtures.readKey('ec10-cert.pem'), + ], + }); + }, { + code: 'ERR_OSSL_X509_KEY_TYPE_MISMATCH', + library: 'X.509 certificate routines', + function: 'OPENSSL_internal', + reason: 'KEY_TYPE_MISMATCH', + }); +} + +/** + * BoringSSL does not support caller-initiated renegotiation. Even on a TLS 1.2 + * connection, TLSSocket#renegotiate() returns false and the callback receives + * Node's BoringSSL-specific unsupported-renegotiation error instead of + * entering the native binding or exercising Node's renegotiation-limit logic. + */ +function testRenegotiationUnsupported() { + const server = tls.createServer({ + key: fixtures.readKey('rsa_private.pem'), + cert: fixtures.readKey('rsa_cert.crt'), + maxVersion: 'TLSv1.2', + }, (socket) => socket.resume()); + + server.listen(0, common.mustCall(() => { + const client = tls.connect({ + port: server.address().port, + rejectUnauthorized: false, + maxVersion: 'TLSv1.2', + }, common.mustCall(() => { + const ok = client.renegotiate({}, common.mustCall((err) => { + assert.throws(() => { throw err; }, { + code: 'ERR_TLS_RENEGOTIATION_UNSUPPORTED', + message: 'TLS session renegotiation is unsupported by this TLS ' + + 'implementation', + }); + client.destroy(); + server.close(); + })); + assert.strictEqual(ok, false); + })); + client.on('error', common.mustNotCall()); + })); +} + +/** + * OpenSSL exposes the negotiated ephemeral key type, name, and size for TLS + * clients. With BoringSSL the same ECDHE TLS 1.2 handshake succeeds, but + * getEphemeralKeyInfo() returns null on the server side and an object whose + * fields are undefined on the client side. + */ +function testEphemeralKeyInfoUnsupported() { + const server = tls.createServer({ + key: fixtures.readKey('agent2-key.pem'), + cert: fixtures.readKey('agent2-cert.pem'), + ciphers: 'ECDHE-RSA-AES256-GCM-SHA384', + ecdhCurve: 'prime256v1', + maxVersion: 'TLSv1.2', + }, common.mustCall((socket) => { + assert.strictEqual(socket.getEphemeralKeyInfo(), null); + socket.end(); + })); + + server.listen(0, common.mustCall(() => { + const client = tls.connect({ + port: server.address().port, + rejectUnauthorized: false, + maxVersion: 'TLSv1.2', + }, common.mustCall(() => { + assert.deepStrictEqual(client.getEphemeralKeyInfo(), { + type: undefined, + name: undefined, + size: undefined, + }); + server.close(); + })); + })); +} + +/** + * The protocol matrix tests cover OpenSSL behavior for legacy TLS protocols. + * For BoringSSL we only need to exhibit that a TLSv1-only client cannot connect + * to a server whose minimum protocol is TLS 1.2; the client receives the + * protocol-version alert instead of the OpenSSL version-specific matrix. + */ +function testLegacyProtocolUnsupported() { + const server = tls.createServer({ + key: fixtures.readKey('agent2-key.pem'), + cert: fixtures.readKey('agent2-cert.pem'), + minVersion: 'TLSv1.2', + }, common.mustNotCall()); + + server.on('tlsClientError', common.mustCall()); + server.listen(0, common.mustCall(() => { + const client = tls.connect({ + port: server.address().port, + rejectUnauthorized: false, + secureProtocol: 'TLSv1_method', + }, common.mustNotCall()); + client.on('error', common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_SSL_TLSV1_ALERT_PROTOCOL_VERSION'); + server.close(); + })); + })); +} + +/** + * BoringSSL can load a multi-PFX option well enough to serve the ECDSA + * identity, but it does not provide the same OpenSSL multi-identity selection + * behavior. After the ECDSA handshake succeeds, an RSA-only client fails with + * no shared cipher instead of selecting the RSA identity from the same PFX list. + */ +function testMultiPfxSelectionDifference() { + const server = tls.createServer({ + pfx: [ + { + buf: fixtures.readKey('agent1.pfx'), + passphrase: 'sample', + }, + fixtures.readKey('ec.pfx'), + ], + }, common.mustCallAtLeast((socket) => socket.end(), 1)); + + server.listen(0, common.mustCall(() => { + const ecdsa = tls.connect(server.address().port, { + ciphers: 'ECDHE-ECDSA-AES256-GCM-SHA384', + maxVersion: 'TLSv1.2', + rejectUnauthorized: false, + }, common.mustCall(() => { + assert.strictEqual(ecdsa.getCipher().name, + 'ECDHE-ECDSA-AES256-GCM-SHA384'); + ecdsa.end(); + + server.once('tlsClientError', common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_SSL_NO_SHARED_CIPHER'); + })); + const rsa = tls.connect(server.address().port, { + ciphers: 'ECDHE-RSA-AES256-GCM-SHA384', + maxVersion: 'TLSv1.2', + rejectUnauthorized: false, + }, common.mustNotCall()); + rsa.on('error', common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE'); + server.close(); + })); + })); + })); +} + +/** + * PSK works for TLS 1.2 in BoringSSL, but Node's PSK tests also cover the + * default TLS 1.3 path. In that path BoringSSL does not complete a certificate- + * less PSK-only handshake through Node's current server setup: the server + * reports NO_CERTIFICATE_SET and the client receives an internal-error alert. + */ +function testPskTls13Unsupported() { + const key = Buffer.from('d731ef57be09e5204f0b205b60627028', 'hex'); + let gotClientError = false; + let gotServerError = false; + function maybeClose(server) { + if (gotClientError && gotServerError) + server.close(); + } + + const server = tls.createServer({ + ciphers: 'PSK+HIGH', + pskCallback() { return key; }, + }, common.mustNotCall()); + + server.once('tlsClientError', common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_SSL_NO_CERTIFICATE_SET'); + gotServerError = true; + maybeClose(server); + })); + + server.listen(0, common.mustCall(() => { + const client = tls.connect({ + port: server.address().port, + ciphers: 'PSK+HIGH', + checkServerIdentity() {}, + pskCallback() { + return { psk: key, identity: 'TestUser' }; + }, + }, common.mustNotCall()); + client.on('error', common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_SSL_TLSV1_ALERT_INTERNAL_ERROR'); + gotClientError = true; + maybeClose(server); + })); + })); +} + +/** + * The OpenSSL ticket tests assume that once a TLS 1.3 session is reused, the + * client will not necessarily receive a replacement session event before close. + * BoringSSL emits new session tickets on both the initial and resumed TLS 1.3 + * connections, so the resumed connection still emits at least one 'session' + * event while isSessionReused() is true. + */ +function testTls13SessionTicketSemanticsDiffer() { + const server = tls.createServer({ + key: fixtures.readKey('agent1-key.pem'), + cert: fixtures.readKey('agent1-cert.pem'), + }, (socket) => socket.end()); + + let session; + let secondSessionEvents = 0; + + server.listen(0, common.mustCall(() => { + const first = tls.connect({ + port: server.address().port, + rejectUnauthorized: false, + }, common.mustCall(() => { + assert.strictEqual(first.isSessionReused(), false); + })); + first.on('session', common.mustCallAtLeast((sess) => { + session = sess; + }, 1)); + first.on('close', common.mustCall(() => { + assert(Buffer.isBuffer(session)); + + const second = tls.connect({ + port: server.address().port, + rejectUnauthorized: false, + session, + }, common.mustCall(() => { + assert.strictEqual(second.isSessionReused(), true); + })); + second.on('session', common.mustCallAtLeast(() => { + secondSessionEvents++; + }, 1)); + second.on('close', common.mustCall(() => { + assert(secondSessionEvents > 0); + server.close(); + })); + second.resume(); + })); + first.resume(); + })); +} + +module.exports = { + assertFiniteFieldDheUnsupported, + assertMultiKeyUnsupported, + assertNoCipherMatch, + assertOpenSSLSecurityLevelsUnsupported, + testEphemeralKeyInfoUnsupported, + testLegacyProtocolUnsupported, + testMultiPfxSelectionDifference, + testPskTls13Unsupported, + testRenegotiationUnsupported, + testTls13SessionTicketSemanticsDiffer, +}; diff --git a/test/parallel/test-crypto-dh-stateless.js b/test/parallel/test-crypto-dh-stateless.js index 9e13c62595ef26..0ade828eb2342b 100644 --- a/test/parallel/test-crypto-dh-stateless.js +++ b/test/parallel/test-crypto-dh-stateless.js @@ -6,6 +6,7 @@ if (!common.hasCrypto) const assert = require('assert'); const crypto = require('crypto'); const { hasOpenSSL } = require('../common/crypto'); +const isBoringSSL = process.features.openssl_is_boringssl; // Error code for a key-type mismatch during (EC)DH. The underlying OpenSSL // error code varies by version, and in OpenSSL 4.0 by platform: some builds @@ -212,8 +213,28 @@ function testDHError(options, expected) { })); } -const alicePrivateKey = crypto.createPrivateKey({ - key: '-----BEGIN PRIVATE KEY-----\n' + +{ + const { privateKey, publicKey } = crypto.generateKeyPairSync('ec', { + namedCurve: 'P-256', + }); + + assert.throws(() => crypto.diffieHellman({ privateKey }), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + }); + + assert.throws(() => crypto.diffieHellman({ publicKey }), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + }); +} + +if (isBoringSSL) { + common.printSkipMessage('Skipping finite-field DH KeyObject import and ' + + 'generation tests unsupported by BoringSSL'); +} else { + const alicePrivateKey = crypto.createPrivateKey({ + key: '-----BEGIN PRIVATE KEY-----\n' + 'MIIBoQIBADCB1QYJKoZIhvcNAQMBMIHHAoHBAP//////////yQ/aoiFowjTExmKL\n' + 'gNwc0SkCTgiKZ8x0Agu+pjsTmyJRSgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVt\n' + 'bVHCReSFtXZiXn7G9ExC6aY37WsL/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR\n' + @@ -224,10 +245,10 @@ const alicePrivateKey = crypto.createPrivateKey({ 'iIt9FmvFaaOVe2DupqSr6xzbf/zyON+WF5B5HNVOWXswgpgdUsCyygs98hKy/Xje\n' + 'TGzJUoWInW39t0YgMXenJrkS0m6wol8Rhxx81AGgELNV7EHZqg==\n' + '-----END PRIVATE KEY-----', - format: 'pem' -}); -const alicePublicKey = crypto.createPublicKey({ - key: '-----BEGIN PUBLIC KEY-----\n' + + format: 'pem' + }); + const alicePublicKey = crypto.createPublicKey({ + key: '-----BEGIN PUBLIC KEY-----\n' + 'MIIBnzCB1QYJKoZIhvcNAQMBMIHHAoHBAP//////////yQ/aoiFowjTExmKLgNwc\n' + '0SkCTgiKZ8x0Agu+pjsTmyJRSgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVtbVHC\n' + 'ReSFtXZiXn7G9ExC6aY37WsL/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR7ORb\n' + @@ -238,11 +259,11 @@ const alicePublicKey = crypto.createPublicKey({ 'rDEz8mjIlnvbWpKB9+uYmbjfVoc3leFvUBqfG2In2m23Md1swsPxr3n7g68H66JX\n' + 'iBJKZLQMqNdbY14G9rdKmhhTJrQjC+i7Q/wI8JPhOFzHIGA=\n' + '-----END PUBLIC KEY-----', - format: 'pem' -}); + format: 'pem' + }); -const bobPrivateKey = crypto.createPrivateKey({ - key: '-----BEGIN PRIVATE KEY-----\n' + + const bobPrivateKey = crypto.createPrivateKey({ + key: '-----BEGIN PRIVATE KEY-----\n' + 'MIIBoQIBADCB1QYJKoZIhvcNAQMBMIHHAoHBAP//////////yQ/aoiFowjTExmKL\n' + 'gNwc0SkCTgiKZ8x0Agu+pjsTmyJRSgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVt\n' + 'bVHCReSFtXZiXn7G9ExC6aY37WsL/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR\n' + @@ -253,11 +274,11 @@ const bobPrivateKey = crypto.createPrivateKey({ 'GagGtIy3dV5f4FA0B/2C97jQ1pO16ah8gSLQRKsNpTCw2rqsZusE0rK6RaYAef7H\n' + 'y/0tmLIsHxLIn+WK9CANqMbCWoP4I178BQaqhiOBkNyNZ0ndqA==\n' + '-----END PRIVATE KEY-----', - format: 'pem' -}); + format: 'pem' + }); -const bobPublicKey = crypto.createPublicKey({ - key: '-----BEGIN PUBLIC KEY-----\n' + + const bobPublicKey = crypto.createPublicKey({ + key: '-----BEGIN PUBLIC KEY-----\n' + 'MIIBoDCB1QYJKoZIhvcNAQMBMIHHAoHBAP//////////yQ/aoiFowjTExmKLgNwc\n' + '0SkCTgiKZ8x0Agu+pjsTmyJRSgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVtbVHC\n' + 'ReSFtXZiXn7G9ExC6aY37WsL/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR7ORb\n' + @@ -268,83 +289,83 @@ const bobPublicKey = crypto.createPublicKey({ 'QFKfjzNaJRNMFFd4f2Dn8MSB4yu1xpA1T2i0JSk24vS2H55jx24xhUYtfhT2LJgK\n' + 'JvnaODey/xtY4Kql10ZKf43Lw6gdQC3G8opC9OxVxt9oNR7Z\n' + '-----END PUBLIC KEY-----', - format: 'pem' -}); + format: 'pem' + }); -assert.throws(() => crypto.diffieHellman({ privateKey: alicePrivateKey }), { - name: 'TypeError', - code: 'ERR_INVALID_ARG_TYPE', -}); + assert.throws(() => crypto.diffieHellman({ privateKey: alicePrivateKey }), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + }); -assert.throws(() => crypto.diffieHellman({ publicKey: alicePublicKey }), { - name: 'TypeError', - code: 'ERR_INVALID_ARG_TYPE', -}); + assert.throws(() => crypto.diffieHellman({ publicKey: alicePublicKey }), { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + }); -const privateKey = Buffer.from( - '487CD880159D835FD0A8DBA9848898317283DB07E822741B344AD397BA84CDDD3920A51588' + + const privateKey = Buffer.from( + '487CD880159D835FD0A8DBA9848898317283DB07E822741B344AD397BA84CDDD3920A51588' + 'B891B03B3EBEF3C9F767D921FAC1294D4B5E09CABB6D1DE3EB4527989754FEB64D007EBBDA' + '2E6C8CE7A17EF41DE3C2DFE7CEAAF963199F55D5DBD9A415E77552FE69B7A41D87888B7D16' + '6BC569A3957B60EEA6A4ABEB1CDB7FFCF238DF961790791CD54E597B3082981D52C0B2CA0B' + '3DF212B2FD78DE4C6CC95285889D6DFDB746203177A726B912D26EB0A25F11871C7CD401A0' + '10B355EC41D9AA', 'hex'); -const publicKey = Buffer.from( - '8b6ea8abccff18d4819b7ce280db7b480edc02b5016d3c4835af622d85a9e9bc6bbc22b00d' + + const publicKey = Buffer.from( + '8b6ea8abccff18d4819b7ce280db7b480edc02b5016d3c4835af622d85a9e9bc6bbc22b00d' + '0c0848ddfafd0530f275007bc691c8cb74a189fecbabd63f0e4e94ef932eb51e94c5456800' + 'c4ce8628987d335466f4b16e1a04df21682d266eb3edf50b21802be3af58443c49da40529f' + '8f335a25134c1457787f60e7f0c481e32bb5c690354f68b4252936e2f4b61f9e63c76e3185' + '462d7e14f62c980a26f9da3837b2ff1b58e0aaa5d7464a7f8dcbc3a81d402dc6f28a42f4ec' + '55c6df68351ed9', 'hex'); -const group = crypto.getDiffieHellman('modp5'); -const dh = crypto.createDiffieHellman(group.getPrime(), group.getGenerator()); -dh.setPrivateKey(privateKey); + const group = crypto.getDiffieHellman('modp5'); + const dh = crypto.createDiffieHellman(group.getPrime(), group.getGenerator()); + dh.setPrivateKey(privateKey); -// Test simple Diffie-Hellman, no curves involved. -test({ publicKey: alicePublicKey, privateKey: alicePrivateKey }, - { publicKey: bobPublicKey, privateKey: bobPrivateKey }, - dh.computeSecret(publicKey)); + // Test simple Diffie-Hellman, no curves involved. + test({ publicKey: alicePublicKey, privateKey: alicePrivateKey }, + { publicKey: bobPublicKey, privateKey: bobPrivateKey }, + dh.computeSecret(publicKey)); -test(crypto.generateKeyPairSync('dh', { group: 'modp5' }), - crypto.generateKeyPairSync('dh', { group: 'modp5' })); + test(crypto.generateKeyPairSync('dh', { group: 'modp5' }), + crypto.generateKeyPairSync('dh', { group: 'modp5' })); -test(crypto.generateKeyPairSync('dh', { group: 'modp5' }), - crypto.generateKeyPairSync('dh', { prime: group.getPrime() })); + test(crypto.generateKeyPairSync('dh', { group: 'modp5' }), + crypto.generateKeyPairSync('dh', { prime: group.getPrime() })); -// DH parameter mismatch tests -{ - const list = [ + // DH parameter mismatch tests + { + const list = [ // Same generator, but different primes. - [{ group: 'modp5' }, { group: 'modp18' }]]; + [{ group: 'modp5' }, { group: 'modp18' }]]; - // TODO(danbev): Take a closer look if there should be a check in OpenSSL3 - // when the dh parameters differ. - if (!hasOpenSSL(3)) { + // TODO(danbev): Take a closer look if there should be a check in OpenSSL3 + // when the dh parameters differ. + if (!hasOpenSSL(3)) { // Same primes, but different generator. - list.push([{ group: 'modp5' }, { prime: group.getPrime(), generator: 5 }]); - // Same generator, but different primes. - list.push([{ primeLength: 1024 }, { primeLength: 1024 }]); - } + list.push([{ group: 'modp5' }, { prime: group.getPrime(), generator: 5 }]); + // Same generator, but different primes. + list.push([{ primeLength: 1024 }, { primeLength: 1024 }]); + } - for (const [params1, params2] of list) { - const options = { - privateKey: crypto.generateKeyPairSync('dh', params1).privateKey, - publicKey: crypto.generateKeyPairSync('dh', params2).publicKey, - }; - testDHError(options, { - name: 'Error', - code: hasOpenSSL(3) ? - 'ERR_OSSL_MISMATCHING_DOMAIN_PARAMETERS' : - 'ERR_OSSL_EVP_DIFFERENT_PARAMETERS' - }); + for (const [params1, params2] of list) { + const options = { + privateKey: crypto.generateKeyPairSync('dh', params1).privateKey, + publicKey: crypto.generateKeyPairSync('dh', params2).publicKey, + }; + testDHError(options, { + name: 'Error', + code: hasOpenSSL(3) ? + 'ERR_OSSL_MISMATCHING_DOMAIN_PARAMETERS' : + 'ERR_OSSL_EVP_DIFFERENT_PARAMETERS' + }); + } } -} -// This key combination will result in an unusually short secret, and should -// not cause an assertion failure. -{ - const shortPrivateKey = crypto.createPrivateKey({ - key: '-----BEGIN PRIVATE KEY-----\n' + + // This key combination will result in an unusually short secret, and should + // not cause an assertion failure. + { + const shortPrivateKey = crypto.createPrivateKey({ + key: '-----BEGIN PRIVATE KEY-----\n' + 'MIIBoQIBADCB1QYJKoZIhvcNAQMBMIHHAoHBAP//////////yQ/aoiFowjTExmKL\n' + 'gNwc0SkCTgiKZ8x0Agu+pjsTmyJRSgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVt\n' + 'bVHCReSFtXZiXn7G9ExC6aY37WsL/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR\n' + @@ -355,9 +376,9 @@ test(crypto.generateKeyPairSync('dh', { group: 'modp5' }), 'RQD0QogW7ejSwMG8hCYibfrvMm0b5PHlwimISyEKh7VtDQ1frYN/Wr9ZbiV+FePJ\n' + '2j6RUKYNj1Pv+B4zdMgiLLjILAs8WUfbHciU21KSJh1izVQaUQ==\n' + '-----END PRIVATE KEY-----' - }); - const shortPublicKey = crypto.createPublicKey({ - key: '-----BEGIN PUBLIC KEY-----\n' + + }); + const shortPublicKey = crypto.createPublicKey({ + key: '-----BEGIN PUBLIC KEY-----\n' + 'MIIBoDCB1QYJKoZIhvcNAQMBMIHHAoHBAP//////////yQ/aoiFowjTExmKLgNwc\n' + '0SkCTgiKZ8x0Agu+pjsTmyJRSgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVtbVHC\n' + 'ReSFtXZiXn7G9ExC6aY37WsL/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR7ORb\n' + @@ -368,19 +389,20 @@ test(crypto.generateKeyPairSync('dh', { group: 'modp5' }), 'taGX4mP3247golVx2DS4viDYs7UtaMdx03dWaP6y5StNUZQlgCIUzL7MYpC16V5y\n' + 'KkFrE+Kp/Z77gEjivaG6YuxVj4GPLxJYbNFVTel42oSVeKuq\n' + '-----END PUBLIC KEY-----', - format: 'pem' - }); + format: 'pem' + }); - testDH({ publicKey: shortPublicKey, privateKey: shortPrivateKey }, - Buffer.from( - '0099d0fa242af5db9ea7330e23937a27db041f79c581500fc7f9976' + + testDH({ publicKey: shortPublicKey, privateKey: shortPrivateKey }, + Buffer.from( + '0099d0fa242af5db9ea7330e23937a27db041f79c581500fc7f9976' + '554d59d5b9ced934778d72e19a1fefc81e9d981013198748c0b5c6c' + '762985eec687dc5bec5c9367b05837daee9d0bcc29024ed7f3abba1' + '2794b65a745117fb0d87bc5b1b2b68c296c3f686cc29e450e4e1239' + - '21f56a5733fe58aabf71f14582954059c2185d342b9b0fa10c2598a' + - '5426c2baee7f9a686fc1e16cd4757c852bf7225a2732250548efe28' + - 'debc26f1acdec51efe23d20786a6f8a14d360803bbc71972e87fd3', - 'hex')); + '21f56a5733fe58aabf71f14582954059c2185d342b9b0fa10c2598a' + + '5426c2baee7f9a686fc1e16cd4757c852bf7225a2732250548efe28' + + 'debc26f1acdec51efe23d20786a6f8a14d360803bbc71972e87fd3', + 'hex')); + } } // Test ECDH. @@ -401,20 +423,25 @@ test(crypto.generateKeyPairSync('ec', { namedCurve: 'P-256' }), }); } -test(crypto.generateKeyPairSync('x448'), - crypto.generateKeyPairSync('x448')); +if (isBoringSSL) { + common.printSkipMessage('Skipping x448 diffieHellman test cases ' + + 'unsupported by BoringSSL'); +} else { + test(crypto.generateKeyPairSync('x448'), + crypto.generateKeyPairSync('x448')); + + { + const options = { + privateKey: crypto.generateKeyPairSync('x448').privateKey, + publicKey: crypto.generateKeyPairSync('x25519').publicKey, + }; + testDHError(options, { code: keyTypeMismatchCode }); + } +} test(crypto.generateKeyPairSync('x25519'), crypto.generateKeyPairSync('x25519')); -{ - const options = { - privateKey: crypto.generateKeyPairSync('x448').privateKey, - publicKey: crypto.generateKeyPairSync('x25519').publicKey, - }; - testDHError(options, { code: keyTypeMismatchCode }); -} - // Test all key encoding formats for (const { privateKey: alicePriv, publicKey: bobPub } of [ crypto.generateKeyPairSync('ec', { namedCurve: 'P-256' }), @@ -514,7 +541,7 @@ for (const { privateKey: alicePriv, publicKey: bobPub } of [ { const ec256 = crypto.generateKeyPairSync('ec', { namedCurve: 'P-256' }); const ec384 = crypto.generateKeyPairSync('ec', { namedCurve: 'P-384' }); - const x448 = crypto.generateKeyPairSync('x448'); + const x448 = isBoringSSL ? null : crypto.generateKeyPairSync('x448'); const x25519 = crypto.generateKeyPairSync('x25519'); const ed25519 = crypto.generateKeyPairSync('ed25519'); @@ -564,18 +591,21 @@ for (const { privateKey: alicePriv, publicKey: bobPub } of [ /^ERR_OSSL_EVP_(OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE|INTERNAL_ERROR)$/ : 'ERR_OSSL_EVP_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE' }); - // Incompatible key types (x448 + x25519) - testDHError({ - privateKey: privKey(x448.privateKey), - publicKey: pubKey(x25519.publicKey), - }, { code: keyTypeMismatchCode }); + if (!isBoringSSL) { + // Incompatible key types (x448 + x25519) + testDHError({ + privateKey: privKey(x448.privateKey), + publicKey: pubKey(x25519.publicKey), + }, { code: keyTypeMismatchCode }); + } // Zero x25519 public key testDHError({ privateKey: privKey(x25519.privateKey), publicKey: pubKey(zeroX25519PublicKey), - }, hasOpenSSL(3) ? - { code: 'ERR_OSSL_FAILED_DURING_DERIVATION' } : - { message: /Deriving bits failed/ }); + }, isBoringSSL ? { code: 'ERR_OSSL_EVP_INVALID_PEER_KEY' } : + hasOpenSSL(3) ? + { code: 'ERR_OSSL_FAILED_DURING_DERIVATION' } : + { message: /Deriving bits failed/ }); } } diff --git a/test/parallel/test-crypto-keygen.js b/test/parallel/test-crypto-keygen.js index 7911520af34481..751029e1921edb 100644 --- a/test/parallel/test-crypto-keygen.js +++ b/test/parallel/test-crypto-keygen.js @@ -15,6 +15,7 @@ const { const { inspect } = require('util'); const { hasOpenSSL3 } = require('../common/crypto'); +const isBoringSSL = process.features.openssl_is_boringssl; // Test invalid parameter encoding. { @@ -361,13 +362,24 @@ const { hasOpenSSL3 } = require('../common/crypto'); // Test invalid exponents. (caught by OpenSSL) for (const publicExponent of [1, 1 + 0x10001]) { - generateKeyPair('rsa', { - modulusLength: 4096, - publicExponent - }, common.mustCall((err) => { - assert.strictEqual(err.name, 'Error'); - assert.match(err.message, hasOpenSSL3 ? /exponent/ : /bad e value/); - })); + if (isBoringSSL) { + assert.throws(() => generateKeyPair('rsa', { + modulusLength: 4096, + publicExponent + }, common.mustNotCall()), { + name: 'RangeError', + code: 'ERR_OUT_OF_RANGE', + message: 'publicExponent is invalid', + }); + } else { + generateKeyPair('rsa', { + modulusLength: 4096, + publicExponent + }, common.mustCall((err) => { + assert.strictEqual(err.name, 'Error'); + assert.match(err.message, hasOpenSSL3 ? /exponent/ : /bad e value/); + })); + } } } @@ -494,16 +506,21 @@ const { hasOpenSSL3 } = require('../common/crypto'); }); })); - generateKeyPair('ec', { - namedCurve: 'secp256k1', - }, common.mustSucceed((publicKey, privateKey) => { - assert.deepStrictEqual(publicKey.asymmetricKeyDetails, { - namedCurve: 'secp256k1' - }); - assert.deepStrictEqual(privateKey.asymmetricKeyDetails, { - namedCurve: 'secp256k1' - }); - })); + if (isBoringSSL) { + common.printSkipMessage('Skipping secp256k1 keygen test case ' + + 'unsupported by BoringSSL'); + } else { + generateKeyPair('ec', { + namedCurve: 'secp256k1', + }, common.mustSucceed((publicKey, privateKey) => { + assert.deepStrictEqual(publicKey.asymmetricKeyDetails, { + namedCurve: 'secp256k1' + }); + assert.deepStrictEqual(privateKey.asymmetricKeyDetails, { + namedCurve: 'secp256k1' + }); + })); + } } { diff --git a/test/parallel/test-crypto.js b/test/parallel/test-crypto.js index fbb065dd442876..46f4571b33dfe8 100644 --- a/test/parallel/test-crypto.js +++ b/test/parallel/test-crypto.js @@ -62,7 +62,7 @@ assert.throws(() => { // Throws general Error, so there is no opensslErrorStack property. return err instanceof Error && err.name === 'Error' && - /^Error: mac verify failure$/.test(err) && + /^Error: (mac verify failure|INCORRECT_PASSWORD)$/.test(err) && !('opensslErrorStack' in err); }); @@ -72,7 +72,7 @@ assert.throws(() => { // Throws general Error, so there is no opensslErrorStack property. return err instanceof Error && err.name === 'Error' && - /^Error: mac verify failure$/.test(err) && + /^Error: (mac verify failure|INCORRECT_PASSWORD)$/.test(err) && !('opensslErrorStack' in err); }); @@ -82,7 +82,7 @@ assert.throws(() => { // Throws general Error, so there is no opensslErrorStack property. return err instanceof Error && err.name === 'Error' && - /^Error: not enough data$/.test(err) && + /^Error: (not enough data|BAD_PKCS12_DATA)$/.test(err) && !('opensslErrorStack' in err); }); @@ -211,49 +211,72 @@ assert.throws(() => { ].join('\n'); crypto.createSign('SHA256').update('test').sign(priv); }, (err) => { - if (!hasOpenSSL3) - assert.ok(!('opensslErrorStack' in err)); - assert.throws(() => { throw err; }, hasOpenSSL3 ? { - name: 'Error', - message: 'error:02000070:rsa routines::digest too big for rsa key', - library: 'rsa routines', - } : { - name: 'Error', - message: /routines:RSA_sign:digest too big for rsa key$/, - library: /rsa routines/i, - function: 'RSA_sign', - reason: /digest[\s_]too[\s_]big[\s_]for[\s_]rsa[\s_]key/i, - code: 'ERR_OSSL_RSA_DIGEST_TOO_BIG_FOR_RSA_KEY' - }); + if (process.features.openssl_is_boringssl) { + // BoringSSL rejects the tiny RSA key while decoding it, before signing. + assert.throws(() => { throw err; }, { + name: 'Error', + message: 'error:06000066:public key routines:OPENSSL_internal:' + + 'DECODE_ERROR', + library: 'public key routines', + function: 'OPENSSL_internal', + reason: 'DECODE_ERROR', + code: 'ERR_OSSL_EVP_DECODE_ERROR' + }); + assert(Array.isArray(err.opensslErrorStack)); + assert(err.opensslErrorStack.length > 0); + } else { + if (!hasOpenSSL3) + assert.ok(!('opensslErrorStack' in err)); + assert.throws(() => { throw err; }, hasOpenSSL3 ? { + name: 'Error', + message: 'error:02000070:rsa routines::digest too big for rsa key', + library: 'rsa routines', + } : { + name: 'Error', + message: /routines:RSA_sign:digest too big for rsa key$/, + library: /rsa routines/i, + function: 'RSA_sign', + reason: /digest[\s_]too[\s_]big[\s_]for[\s_]rsa[\s_]key/i, + code: 'ERR_OSSL_RSA_DIGEST_TOO_BIG_FOR_RSA_KEY' + }); + } return true; }); if (!hasOpenSSL3) { - assert.throws(() => { - // The correct header inside `rsa_private_pkcs8_bad.pem` should have been - // -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY----- - // instead of - // -----BEGIN RSA PRIVATE KEY----- and -----END RSA PRIVATE KEY----- - const sha1_privateKey = fixtures.readKey('rsa_private_pkcs8_bad.pem', - 'ascii'); - // This would inject errors onto OpenSSL's error stack - crypto.createSign('sha1').sign(sha1_privateKey); - }, (err) => { - // Do the standard checks, but then do some custom checks afterwards. - assert.throws(() => { throw err; }, { - message: 'error:0D0680A8:asn1 encoding routines:asn1_check_tlen:' + - 'wrong tag', - library: 'asn1 encoding routines', - function: 'asn1_check_tlen', - reason: 'wrong tag', - code: 'ERR_OSSL_ASN1_WRONG_TAG', + // The correct header inside `rsa_private_pkcs8_bad.pem` should have been + // -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY----- + // instead of + // -----BEGIN RSA PRIVATE KEY----- and -----END RSA PRIVATE KEY----- + const sha1_privateKey = fixtures.readKey('rsa_private_pkcs8_bad.pem', + 'ascii'); + + if (process.features.openssl_is_boringssl) { + // BoringSSL accepts the PKCS#8 payload despite the legacy PEM label. + const signature = crypto.createSign('sha1').sign(sha1_privateKey); + assert(Buffer.isBuffer(signature)); + assert.strictEqual(signature.length, 256); + } else { + assert.throws(() => { + // This would inject errors onto OpenSSL's error stack + crypto.createSign('sha1').sign(sha1_privateKey); + }, (err) => { + // Do the standard checks, but then do some custom checks afterwards. + assert.throws(() => { throw err; }, { + message: 'error:0D0680A8:asn1 encoding routines:asn1_check_tlen:' + + 'wrong tag', + library: 'asn1 encoding routines', + function: 'asn1_check_tlen', + reason: 'wrong tag', + code: 'ERR_OSSL_ASN1_WRONG_TAG', + }); + // Throws crypto error, so there is an opensslErrorStack property. + // The openSSL stack should have content. + assert(Array.isArray(err.opensslErrorStack)); + assert(err.opensslErrorStack.length > 0); + return true; }); - // Throws crypto error, so there is an opensslErrorStack property. - // The openSSL stack should have content. - assert(Array.isArray(err.opensslErrorStack)); - assert(err.opensslErrorStack.length > 0); - return true; - }); + } } // Make sure memory isn't released before being returned diff --git a/test/parallel/test-https-agent-session-reuse.js b/test/parallel/test-https-agent-session-reuse.js index 485f4b1ca308c9..c5b7b78b8e0272 100644 --- a/test/parallel/test-https-agent-session-reuse.js +++ b/test/parallel/test-https-agent-session-reuse.js @@ -5,6 +5,11 @@ const assert = require('assert'); if (!common.hasCrypto) common.skip('missing crypto'); +if (process.features.openssl_is_boringssl) { + require('../common/boringssl').testTls13SessionTicketSemanticsDiffer(); + return; +} + const https = require('https'); const crypto = require('crypto'); const fixtures = require('../common/fixtures'); diff --git a/test/parallel/test-https-client-renegotiation-limit.js b/test/parallel/test-https-client-renegotiation-limit.js index 6614090e737614..729176b7c1aa21 100644 --- a/test/parallel/test-https-client-renegotiation-limit.js +++ b/test/parallel/test-https-client-renegotiation-limit.js @@ -25,6 +25,11 @@ if (!common.hasCrypto) { common.skip('missing crypto'); } +if (process.features.openssl_is_boringssl) { + require('../common/boringssl').testRenegotiationUnsupported(); + return; +} + const assert = require('assert'); const tls = require('tls'); const https = require('https'); diff --git a/test/parallel/test-https-foafssl.js b/test/parallel/test-https-foafssl.js index ffa44f218b935d..a191bdcf32b73e 100644 --- a/test/parallel/test-https-foafssl.js +++ b/test/parallel/test-https-foafssl.js @@ -56,8 +56,8 @@ const server = https.createServer(options, common.mustCall(function(req, res) { cert = req.connection.getPeerCertificate(); assert.strictEqual(cert.subjectaltname, webIdUrl); - assert.strictEqual(cert.exponent, exponent); - assert.strictEqual(cert.modulus, modulus); + assert.strictEqual(cert.exponent.toLowerCase(), exponent.toLowerCase()); + assert.strictEqual(cert.modulus.toLowerCase(), modulus.toLowerCase()); res.writeHead(200, { 'content-type': 'text/plain' }); res.end(body, () => { console.log('stream finished'); }); console.log('sent response'); diff --git a/test/parallel/test-https-options-boolean-check.js b/test/parallel/test-https-options-boolean-check.js index 9740704e169f1e..fa02a165b80f10 100644 --- a/test/parallel/test-https-options-boolean-check.js +++ b/test/parallel/test-https-options-boolean-check.js @@ -40,9 +40,23 @@ const keyDataView = toDataView(keyBuff); const certDataView = toDataView(certBuff); const caArrDataView = toDataView(caCert); +function filterBoringSSLKeyCertArrayCases(options, setName) { + if (!process.features.openssl_is_boringssl) + return options; + + // The array-valued cases exercise multi-identity key/cert handling. + // BoringSSL may reject those cases with backend key/cert mismatch errors + // before the boolean/type validation this test is targeting. Keep the scalar + // cases so https.createServer() option type validation is still covered. + common.printSkipMessage( + `BoringSSL: skipping ${setName} key/cert array cases`); + return options.filter(([key, cert]) => !Array.isArray(key) && + !Array.isArray(cert)); +} + // Checks to ensure https.createServer doesn't throw an error // Format ['key', 'cert'] -[ +const validOptions = [ [keyBuff, certBuff], [false, certBuff], [keyBuff, false], @@ -62,13 +76,16 @@ const caArrDataView = toDataView(caCert); [false, [certStr, certStr2]], [[{ pem: keyBuff }], false], [[{ pem: keyBuff }, { pem: keyBuff }], false], -].forEach(([key, cert]) => { - https.createServer({ key, cert }); -}); +]; + +filterBoringSSLKeyCertArrayCases(validOptions, 'valid') + .forEach(([key, cert]) => { + https.createServer({ key, cert }); + }); // Checks to ensure https.createServer predictably throws an error // Format ['key', 'cert', 'expected message'] -[ +const invalidKeyOptions = [ [true, certBuff], [true, certStr], [true, certArrBuff], @@ -81,7 +98,10 @@ const caArrDataView = toDataView(caCert); [[true, keyStr2], [certStr, certStr2], 0], [[true, false], [certBuff, certBuff2], 0], [true, [certBuff, certBuff2]], -].forEach(([key, cert, index]) => { +]; + +for (const [key, cert, index] of + filterBoringSSLKeyCertArrayCases(invalidKeyOptions, 'invalid key')) { const val = index === undefined ? key : key[index]; assert.throws(() => { https.createServer({ key, cert }); @@ -92,9 +112,9 @@ const caArrDataView = toDataView(caCert); 'instance of Buffer, TypedArray, or DataView.' + common.invalidArgTypeHelper(val) }); -}); +} -[ +const invalidCertOptions = [ [keyBuff, true], [keyStr, true], [keyArrBuff, true], @@ -107,7 +127,10 @@ const caArrDataView = toDataView(caCert); [[keyStr, keyStr2], [certStr, true], 1], [[keyStr, keyStr2], [true, false], 0], [[keyStr, keyStr2], true], -].forEach(([key, cert, index]) => { +]; + +for (const [key, cert, index] of + filterBoringSSLKeyCertArrayCases(invalidCertOptions, 'invalid cert')) { const val = index === undefined ? cert : cert[index]; assert.throws(() => { https.createServer({ key, cert }); @@ -118,7 +141,7 @@ const caArrDataView = toDataView(caCert); 'instance of Buffer, TypedArray, or DataView.' + common.invalidArgTypeHelper(val) }); -}); +} // Checks to ensure https.createServer works with the CA parameter // Format ['key', 'cert', 'ca'] diff --git a/test/parallel/test-tls-alert.js b/test/parallel/test-tls-alert.js index 23c92e7293458f..64b7080e39ba25 100644 --- a/test/parallel/test-tls-alert.js +++ b/test/parallel/test-tls-alert.js @@ -48,6 +48,33 @@ const server = tls.Server({ key: loadPEM('agent2-key'), cert: loadPEM('agent2-cert') }, null).listen(0, common.mustCall(() => { + if (process.features.openssl_is_boringssl) { + let gotClientError = false; + let gotServerError = false; + function maybeClose() { + if (gotClientError && gotServerError) + server.close(); + } + + server.once('tlsClientError', common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_SSL_UNSUPPORTED_PROTOCOL'); + gotServerError = true; + maybeClose(); + })); + + const client = tls.connect({ + port: server.address().port, + rejectUnauthorized: false, + secureProtocol: 'TLSv1_1_method', + }, common.mustNotCall()); + client.once('error', common.mustCall((err) => { + assert.strictEqual(err.code, 'ERR_SSL_TLSV1_ALERT_PROTOCOL_VERSION'); + gotClientError = true; + maybeClose(); + })); + return; + } + const args = ['s_client', '-quiet', '-tls1_1', '-cipher', (hasOpenSSL(3, 1) ? 'DEFAULT:@SECLEVEL=0' : 'DEFAULT'), '-connect', `127.0.0.1:${server.address().port}`]; diff --git a/test/parallel/test-tls-client-auth.js b/test/parallel/test-tls-client-auth.js index 67aed40914c9fe..517054c6e290dc 100644 --- a/test/parallel/test-tls-client-auth.js +++ b/test/parallel/test-tls-client-auth.js @@ -111,7 +111,10 @@ if (tls.DEFAULT_MAX_VERSION === 'TLSv1.3') connect({ // 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) => { - assert.strictEqual(err.code, 'ERR_SSL_TLSV13_ALERT_CERTIFICATE_REQUIRED'); + const expectedErr = process.features.openssl_is_boringssl ? + 'ERR_SSL_TLSV1_ALERT_CERTIFICATE_REQUIRED' : + 'ERR_SSL_TLSV13_ALERT_CERTIFICATE_REQUIRED'; + assert.strictEqual(err.code, expectedErr); cleanup(); })); })); diff --git a/test/parallel/test-tls-client-getephemeralkeyinfo.js b/test/parallel/test-tls-client-getephemeralkeyinfo.js index 19728e3733d868..2107d024012c4d 100644 --- a/test/parallel/test-tls-client-getephemeralkeyinfo.js +++ b/test/parallel/test-tls-client-getephemeralkeyinfo.js @@ -2,6 +2,12 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); + +if (process.features.openssl_is_boringssl) { + require('../common/boringssl').testEphemeralKeyInfoUnsupported(); + return; +} + const fixtures = require('../common/fixtures'); const { hasOpenSSL } = require('../common/crypto'); diff --git a/test/parallel/test-tls-client-mindhsize.js b/test/parallel/test-tls-client-mindhsize.js index cd7b16ea566fe8..fa494c583a2f3b 100644 --- a/test/parallel/test-tls-client-mindhsize.js +++ b/test/parallel/test-tls-client-mindhsize.js @@ -85,21 +85,25 @@ function testDHE3072() { test(3072, false, null); } -if (hasOpenSSL(4, 0)) { - // OpenSSL 4.0 implements RFC 7919 FFDHE negotiation for TLS 1.2 and - // ignores the server-supplied dhparam in favor of FFDHE-2048. The 3072 - // success case is therefore replaced by a 2048 success case. - testDHE2048(true, () => test(2048, false, null, 2048)); -} else if (secLevel > 1) { - // Minimum size for OpenSSL security level 2 and above is 2048 by default - testDHE2048(true, testDHE3072); +if (!process.features.openssl_is_boringssl) { + if (hasOpenSSL(4, 0)) { + // OpenSSL 4.0 implements RFC 7919 FFDHE negotiation for TLS 1.2 and + // ignores the server-supplied dhparam in favor of FFDHE-2048. The 3072 + // success case is therefore replaced by a 2048 success case. + testDHE2048(true, () => test(2048, false, null, 2048)); + } else if (secLevel > 1) { + // Minimum size for OpenSSL security level 2 and above is 2048 by default + testDHE2048(true, testDHE3072); + } else { + testDHE1024(); + } + + assert.throws(() => test(512, true, common.mustNotCall()), + /DH parameter is less than 1024 bits/); } else { - testDHE1024(); + require('../common/boringssl').assertFiniteFieldDheUnsupported(); } -assert.throws(() => test(512, true, common.mustNotCall()), - /DH parameter is less than 1024 bits/); - for (const minDHSize of [0, -1, -Infinity, NaN]) { assert.throws(() => { tls.connect({ minDHSize }); @@ -118,7 +122,9 @@ for (const minDHSize of [true, false, null, undefined, {}, [], '', '1']) { }); } -process.on('exit', function() { - assert.strictEqual(nsuccess, 1); - assert.strictEqual(nerror, 1); -}); +if (!process.features.openssl_is_boringssl) { + process.on('exit', function() { + assert.strictEqual(nsuccess, 1); + assert.strictEqual(nerror, 1); + }); +} diff --git a/test/parallel/test-tls-client-reject.js b/test/parallel/test-tls-client-reject.js index 68922e3690eac0..cff0aabc89a774 100644 --- a/test/parallel/test-tls-client-reject.js +++ b/test/parallel/test-tls-client-reject.js @@ -30,7 +30,8 @@ const fixtures = require('../common/fixtures'); const options = { key: fixtures.readKey('rsa_private.pem'), - cert: fixtures.readKey('rsa_cert.crt') + cert: fixtures.readKey('rsa_cert.crt'), + ...(process.features.openssl_is_boringssl ? { maxVersion: 'TLSv1.2' } : {}), }; const server = tls.createServer(options, function(socket) { @@ -46,7 +47,8 @@ function unauthorized() { const socket = tls.connect({ port: server.address().port, servername: 'localhost', - rejectUnauthorized: false + rejectUnauthorized: false, + ...(process.features.openssl_is_boringssl ? { maxVersion: 'TLSv1.2' } : {}), }, common.mustCall(function() { let _data; assert(!socket.authorized); @@ -67,7 +69,8 @@ function unauthorized() { function rejectUnauthorized() { console.log('reject unauthorized'); const socket = tls.connect(server.address().port, { - servername: 'localhost' + servername: 'localhost', + ...(process.features.openssl_is_boringssl ? { maxVersion: 'TLSv1.2' } : {}), }, common.mustNotCall()); socket.on('data', common.mustNotCall()); socket.on('error', common.mustCall(function(err) { @@ -80,7 +83,8 @@ function rejectUnauthorizedUndefined() { console.log('reject unauthorized undefined'); const socket = tls.connect(server.address().port, { servername: 'localhost', - rejectUnauthorized: undefined + rejectUnauthorized: undefined, + ...(process.features.openssl_is_boringssl ? { maxVersion: 'TLSv1.2' } : {}), }, common.mustNotCall()); socket.on('data', common.mustNotCall()); socket.on('error', common.mustCall(function(err) { @@ -93,7 +97,8 @@ function authorized() { console.log('connect authorized'); const socket = tls.connect(server.address().port, { ca: [fixtures.readKey('rsa_cert.crt')], - servername: 'localhost' + servername: 'localhost', + ...(process.features.openssl_is_boringssl ? { maxVersion: 'TLSv1.2' } : {}), }, common.mustCall(function() { console.log('... authorized'); assert(socket.authorized); diff --git a/test/parallel/test-tls-client-renegotiation-13.js b/test/parallel/test-tls-client-renegotiation-13.js index 5afa8389ed37ca..80c4753d065ec1 100644 --- a/test/parallel/test-tls-client-renegotiation-13.js +++ b/test/parallel/test-tls-client-renegotiation-13.js @@ -32,14 +32,22 @@ connect({ assert.strictEqual(client.getProtocol(), 'TLSv1.3'); const ok = client.renegotiate({}, common.mustCall((err) => { - assert.throws(() => { throw err; }, { - message: hasOpenSSL3 ? - 'error:0A00010A:SSL routines::wrong ssl version' : - 'error:1420410A:SSL routines:SSL_renegotiate:wrong ssl version', - code: 'ERR_SSL_WRONG_SSL_VERSION', - library: 'SSL routines', - reason: 'wrong ssl version', - }); + if (process.features.openssl_is_boringssl) { + assert.throws(() => { throw err; }, { + message: 'TLS session renegotiation is unsupported by this TLS ' + + 'implementation', + code: 'ERR_TLS_RENEGOTIATION_UNSUPPORTED', + }); + } else { + assert.throws(() => { throw err; }, { + message: hasOpenSSL3 ? + 'error:0A00010A:SSL routines::wrong ssl version' : + 'error:1420410A:SSL routines:SSL_renegotiate:wrong ssl version', + code: 'ERR_SSL_WRONG_SSL_VERSION', + library: 'SSL routines', + reason: 'wrong ssl version', + }); + } cleanup(); })); diff --git a/test/parallel/test-tls-client-renegotiation-limit.js b/test/parallel/test-tls-client-renegotiation-limit.js index 86111d6da0b402..9b7f62865b336d 100644 --- a/test/parallel/test-tls-client-renegotiation-limit.js +++ b/test/parallel/test-tls-client-renegotiation-limit.js @@ -31,6 +31,11 @@ if (!opensslCli) { common.skip('node compiled without OpenSSL CLI.'); } +if (process.features.openssl_is_boringssl) { + require('../common/boringssl').testRenegotiationUnsupported(); + return; +} + const assert = require('assert'); const tls = require('tls'); const fixtures = require('../common/fixtures'); diff --git a/test/parallel/test-tls-dhe.js b/test/parallel/test-tls-dhe.js index 03750bc206adbe..b788d153293899 100644 --- a/test/parallel/test-tls-dhe.js +++ b/test/parallel/test-tls-dhe.js @@ -26,6 +26,11 @@ if (!common.hasCrypto) { common.skip('missing crypto'); } +if (process.features.openssl_is_boringssl) { + require('../common/boringssl').assertFiniteFieldDheUnsupported(); + return; +} + const { opensslCli, hasOpenSSL, diff --git a/test/parallel/test-tls-disable-renegotiation.js b/test/parallel/test-tls-disable-renegotiation.js index f91868c6345d71..84a6ead4a5441c 100644 --- a/test/parallel/test-tls-disable-renegotiation.js +++ b/test/parallel/test-tls-disable-renegotiation.js @@ -8,6 +8,11 @@ const fixtures = require('../common/fixtures'); if (!common.hasCrypto) common.skip('missing crypto'); +if (process.features.openssl_is_boringssl) { + require('../common/boringssl').testRenegotiationUnsupported(); + return; +} + const tls = require('tls'); // Renegotiation as a protocol feature was dropped after TLS1.2. diff --git a/test/parallel/test-tls-ecdh-multiple.js b/test/parallel/test-tls-ecdh-multiple.js index ee52f288610956..ed60044197d7da 100644 --- a/test/parallel/test-tls-ecdh-multiple.js +++ b/test/parallel/test-tls-ecdh-multiple.js @@ -26,7 +26,7 @@ function loadPEM(n) { // OpenSSL 4.0 disables support for deprecated elliptic curves from RFC 8422 // (including secp256k1) by default. -const ecdhCurve = hasOpenSSL(4, 0) ? +const ecdhCurve = process.features.openssl_is_boringssl || hasOpenSSL(4, 0) ? 'prime256v1:secp521r1' : 'secp256k1:prime256v1:secp521r1'; @@ -67,7 +67,7 @@ const server = tls.createServer(options, (conn) => { } // Deprecated RFC 8422 curves are disabled by default in OpenSSL 4.0. - if (hasOpenSSL(4, 0)) { + if (process.features.openssl_is_boringssl || hasOpenSSL(4, 0)) { unsupportedCurves.push('secp256k1'); } diff --git a/test/parallel/test-tls-empty-sni-context.js b/test/parallel/test-tls-empty-sni-context.js index e4136ff71e1d52..6ecdfbeecbe3c9 100644 --- a/test/parallel/test-tls-empty-sni-context.js +++ b/test/parallel/test-tls-empty-sni-context.js @@ -16,7 +16,7 @@ const options = { const server = tls.createServer(options, (c) => { assert.fail('Should not be called'); }).on('tlsClientError', common.mustCall((err, c) => { - assert.match(err.message, /no suitable signature algorithm/i); + assert.match(err.message, /no suitable signature algorithm|NO_CERTIFICATE_SET/i); server.close(); })).listen(0, common.mustCall(() => { const c = tls.connect({ @@ -26,9 +26,10 @@ const server = tls.createServer(options, (c) => { }, common.mustNotCall()); c.on('error', common.mustCall((err) => { - 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'; + const expectedErr = process.features.openssl_is_boringssl ? + 'ERR_SSL_TLSV1_ALERT_INTERNAL_ERROR' : 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(err.code, expectedErr); })); })); diff --git a/test/parallel/test-tls-finished.js b/test/parallel/test-tls-finished.js index 8b52934b049d95..b23b4567d27ec6 100644 --- a/test/parallel/test-tls-finished.js +++ b/test/parallel/test-tls-finished.js @@ -20,7 +20,8 @@ const msg = {}; const pem = (n) => fixtures.readKey(`${n}.pem`); const server = tls.createServer({ key: pem('agent1-key'), - cert: pem('agent1-cert') + cert: pem('agent1-cert'), + ...(process.features.openssl_is_boringssl ? { maxVersion: 'TLSv1.2' } : {}), }, common.mustCall((alice) => { msg.server = { alice: alice.getFinished(), @@ -32,7 +33,8 @@ const server = tls.createServer({ server.listen(0, common.mustCall(() => { const bob = tls.connect({ port: server.address().port, - rejectUnauthorized: false + rejectUnauthorized: false, + ...(process.features.openssl_is_boringssl ? { maxVersion: 'TLSv1.2' } : {}), }, common.mustCall(() => { msg.client = { alice: bob.getPeerFinished(), diff --git a/test/parallel/test-tls-getcipher.js b/test/parallel/test-tls-getcipher.js index 4d5042d6e6beab..2d4de5639afb70 100644 --- a/test/parallel/test-tls-getcipher.js +++ b/test/parallel/test-tls-getcipher.js @@ -36,27 +36,42 @@ const options = { honorCipherOrder: true }; +const isBoringSSL = process.features.openssl_is_boringssl; let clients = 0; +const expectedClients = isBoringSSL ? 1 : 2; const server = tls.createServer(options, common.mustCall(() => { if (--clients === 0) server.close(); -}, 2)); +}, expectedClients)); server.listen(0, '127.0.0.1', common.mustCall(function() { - clients++; - tls.connect({ - host: '127.0.0.1', - port: this.address().port, - ciphers: 'AES256-SHA256', - rejectUnauthorized: false, - maxVersion: 'TLSv1.2', - }, common.mustCall(function() { - const cipher = this.getCipher(); - assert.strictEqual(cipher.name, 'AES256-SHA256'); - assert.strictEqual(cipher.standardName, 'TLS_RSA_WITH_AES_256_CBC_SHA256'); - assert.strictEqual(cipher.version, 'TLSv1.2'); - this.end(); - })); + if (isBoringSSL) { + // BoringSSL does not provide this static RSA TLS 1.2 cipher suite on + // Node's supported cipher surface, so keep the OpenSSL getCipher() + // assertion below limited to backends that can create the context. + common.printSkipMessage('BoringSSL does not provide AES256-SHA256'); + assert.throws(() => tls.createSecureContext({ ciphers: 'AES256-SHA256' }), { + code: 'ERR_SSL_NO_CIPHER_MATCH', + library: 'SSL routines', + function: 'OPENSSL_internal', + reason: 'NO_CIPHER_MATCH', + }); + } else { + clients++; + tls.connect({ + host: '127.0.0.1', + port: this.address().port, + ciphers: 'AES256-SHA256', + rejectUnauthorized: false, + maxVersion: 'TLSv1.2', + }, common.mustCall(function() { + const cipher = this.getCipher(); + assert.strictEqual(cipher.name, 'AES256-SHA256'); + assert.strictEqual(cipher.standardName, 'TLS_RSA_WITH_AES_256_CBC_SHA256'); + assert.strictEqual(cipher.version, 'TLSv1.2'); + this.end(); + })); + } clients++; tls.connect({ @@ -70,7 +85,9 @@ server.listen(0, '127.0.0.1', common.mustCall(function() { assert.strictEqual(cipher.name, 'ECDHE-RSA-AES256-GCM-SHA384'); assert.strictEqual(cipher.standardName, 'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384'); - assert.strictEqual(cipher.version, 'TLSv1.2'); + assert.strictEqual(cipher.version, isBoringSSL ? + 'TLSv1/SSLv3' : + 'TLSv1.2'); this.end(); })); })); @@ -90,9 +107,14 @@ tls.createServer({ rejectUnauthorized: false }, common.mustCall(() => { const cipher = client.getCipher(); - assert.strictEqual(cipher.name, 'TLS_AES_256_GCM_SHA384'); + const expectedCipher = isBoringSSL ? + 'TLS_AES_128_GCM_SHA256' : + 'TLS_AES_256_GCM_SHA384'; + assert.strictEqual(cipher.name, expectedCipher); assert.strictEqual(cipher.standardName, cipher.name); - assert.strictEqual(cipher.version, 'TLSv1.3'); + assert.strictEqual(cipher.version, isBoringSSL ? + 'TLSv1/SSLv3' : + 'TLSv1.3'); client.end(); })); })); diff --git a/test/parallel/test-tls-getprotocol.js b/test/parallel/test-tls-getprotocol.js index 5fe46c43c376cf..2945ff99b5a290 100644 --- a/test/parallel/test-tls-getprotocol.js +++ b/test/parallel/test-tls-getprotocol.js @@ -12,7 +12,7 @@ const assert = require('assert'); const tls = require('tls'); const fixtures = require('../common/fixtures'); -const clientConfigs = [ +let clientConfigs = [ { secureProtocol: 'TLSv1_method', version: 'TLSv1', @@ -27,6 +27,14 @@ const clientConfigs = [ }, ]; +if (process.features.openssl_is_boringssl) { + // Remove the TLSv1 and TLSv1.1 cases. BoringSSL does not negotiate those + // legacy protocols in this configuration; keep TLSv1.2 to cover getProtocol() + // on a successful BoringSSL TLS handshake. + common.printSkipMessage('BoringSSL: skipping TLSv1/TLSv1.1 getProtocol cases'); + clientConfigs = clientConfigs.filter(({ version }) => version === 'TLSv1.2'); +} + const serverConfig = { secureProtocol: 'TLS_method', key: fixtures.readKey('agent2-key.pem'), diff --git a/test/parallel/test-tls-handshake-error.js b/test/parallel/test-tls-handshake-error.js index 5547964780cd60..94a21a14975b5d 100644 --- a/test/parallel/test-tls-handshake-error.js +++ b/test/parallel/test-tls-handshake-error.js @@ -20,7 +20,7 @@ const server = tls.createServer({ port: this.address().port, ciphers: 'no-such-cipher' }, common.mustNotCall()); - }, /no cipher match/i); + }, /no[_ ]cipher[_ ]match/i); server.close(); })); diff --git a/test/parallel/test-tls-honorcipherorder.js b/test/parallel/test-tls-honorcipherorder.js index 5f123cd739a4c0..d86a59aa4cdc6d 100644 --- a/test/parallel/test-tls-honorcipherorder.js +++ b/test/parallel/test-tls-honorcipherorder.js @@ -16,14 +16,40 @@ const util = require('util'); // default method is updated in the future const SSL_Method = 'TLSv1_2_method'; const localhost = '127.0.0.1'; +const config = process.features.openssl_is_boringssl ? { + serverCiphers: + 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256', + clientPreferenceCiphers: + 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384', + clientPreferredCipher: 'ECDHE-RSA-AES128-GCM-SHA256', + serverPreferredCipher: 'ECDHE-RSA-AES256-GCM-SHA384', + singleCipher: 'ECDHE-RSA-AES128-GCM-SHA256', + defaultCipher: 'ECDHE-RSA-AES256-GCM-SHA384', + limitedDefaultCipher: 'ECDHE-RSA-AES128-GCM-SHA256', + extraCases: [], +} : { + serverCiphers: 'AES256-SHA256:AES128-GCM-SHA256:AES128-SHA256:' + + 'ECDHE-RSA-AES128-GCM-SHA256', + clientPreferenceCiphers: 'AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256', + clientPreferredCipher: 'AES128-GCM-SHA256', + serverPreferredCipher: 'AES256-SHA256', + singleCipher: 'AES128-SHA256', + defaultCipher: 'AES256-SHA256', + limitedDefaultCipher: 'ECDHE-RSA-AES128-GCM-SHA256', + extraCases: [ + // Server has the preference of cipher suites. AES128-GCM-SHA256 is given + // higher priority over AES128-SHA256 among client cipher suites. + [true, 'AES128-SHA256:AES128-GCM-SHA256', 'AES128-GCM-SHA256'], + [undefined, 'AES128-SHA256:AES128-GCM-SHA256', 'AES128-GCM-SHA256'], + ], +}; function test(honorCipherOrder, clientCipher, expectedCipher, defaultCiphers) { const soptions = { secureProtocol: SSL_Method, key: fixtures.readKey('agent2-key.pem'), cert: fixtures.readKey('agent2-cert.pem'), - ciphers: 'AES256-SHA256:AES128-GCM-SHA256:AES128-SHA256:' + - 'ECDHE-RSA-AES128-GCM-SHA256', + ciphers: config.serverCiphers, honorCipherOrder: honorCipherOrder, }; @@ -57,34 +83,27 @@ function test(honorCipherOrder, clientCipher, expectedCipher, defaultCiphers) { } // Client explicitly has the preference of cipher suites, not the default. -test(false, 'AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256', - 'AES128-GCM-SHA256'); +test(false, config.clientPreferenceCiphers, config.clientPreferredCipher); -// Server has the preference of cipher suites, and AES256-SHA256 is -// the server's top choice. -test(true, 'AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256', - 'AES256-SHA256'); -test(undefined, 'AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256', - 'AES256-SHA256'); - -// Server has the preference of cipher suites. AES128-GCM-SHA256 is given -// higher priority over AES128-SHA256 among client cipher suites. -test(true, 'AES128-SHA256:AES128-GCM-SHA256', 'AES128-GCM-SHA256'); -test(undefined, 'AES128-SHA256:AES128-GCM-SHA256', 'AES128-GCM-SHA256'); +// Server has the preference of cipher suites. +test(true, config.clientPreferenceCiphers, config.serverPreferredCipher); +test(undefined, config.clientPreferenceCiphers, config.serverPreferredCipher); +for (const args of config.extraCases) { + test(...args); +} // As client has only one cipher, server has no choice, irrespective // of honorCipherOrder. -test(true, 'AES128-SHA256', 'AES128-SHA256'); -test(undefined, 'AES128-SHA256', 'AES128-SHA256'); +test(true, config.singleCipher, config.singleCipher); +test(undefined, config.singleCipher, config.singleCipher); -// Client did not explicitly set ciphers and client offers -// tls.DEFAULT_CIPHERS. All ciphers of the server are included in the -// default list so the negotiated cipher is selected according to the -// server's top preference of AES256-SHA256. -test(true, tls.DEFAULT_CIPHERS, 'AES256-SHA256'); -test(true, null, 'AES256-SHA256'); -test(undefined, null, 'AES256-SHA256'); +// Client did not explicitly set ciphers and client offers tls.DEFAULT_CIPHERS. +// All ciphers of the server are included in the default list so the negotiated +// cipher is selected according to server preference. +test(true, tls.DEFAULT_CIPHERS, config.defaultCipher); +test(true, null, config.defaultCipher); +test(undefined, null, config.defaultCipher); // Ensure that `tls.DEFAULT_CIPHERS` is used when its a limited cipher set. -test(true, null, 'ECDHE-RSA-AES128-GCM-SHA256', 'ECDHE-RSA-AES128-GCM-SHA256'); +test(true, null, config.limitedDefaultCipher, config.limitedDefaultCipher); diff --git a/test/parallel/test-tls-junk-server.js b/test/parallel/test-tls-junk-server.js index 42f089f8f90ed2..b6ff3cd2a467f2 100644 --- a/test/parallel/test-tls-junk-server.js +++ b/test/parallel/test-tls-junk-server.js @@ -24,7 +24,7 @@ server.listen(0, common.mustCall(function() { // Different OpenSSL versions report different errors for junk data on a // TLS connection, depending on which record validation check fires first. const expectedErrorMessage = - /wrong version number|packet length too long|bad record type/; + /wrong[ _]version[ _]number|packet length too long|bad record type/i; req.once('error', common.mustCall(function(err) { assert.match(err.message, expectedErrorMessage); server.close(); diff --git a/test/parallel/test-tls-key-mismatch.js b/test/parallel/test-tls-key-mismatch.js index df8848a03de4a9..797c7c171dc5ff 100644 --- a/test/parallel/test-tls-key-mismatch.js +++ b/test/parallel/test-tls-key-mismatch.js @@ -31,9 +31,11 @@ const { hasOpenSSL3 } = require('../common/crypto'); const assert = require('assert'); const tls = require('tls'); -const errorMessageRegex = hasOpenSSL3 ? - /^Error: error:05800074:x509 certificate routines::key values mismatch$/ : - /^Error: error:0B080074:x509 certificate routines:X509_check_private_key:key values mismatch$/; +const errorMessageRegex = process.features.openssl_is_boringssl ? + /^Error: error:0b000074:X\.509 certificate routines:OPENSSL_internal:KEY_VALUES_MISMATCH$/ : + hasOpenSSL3 ? + /^Error: error:05800074:x509 certificate routines::key values mismatch$/ : + /^Error: error:0B080074:x509 certificate routines:X509_check_private_key:key values mismatch$/; const options = { key: fixtures.readKey('agent1-key.pem'), diff --git a/test/parallel/test-tls-max-send-fragment.js b/test/parallel/test-tls-max-send-fragment.js index 009021045624bb..2e319fcdaeafea 100644 --- a/test/parallel/test-tls-max-send-fragment.js +++ b/test/parallel/test-tls-max-send-fragment.js @@ -60,9 +60,15 @@ const server = tls.createServer({ assert.throws(() => c.setMaxSendFragment(Symbol()), { name: 'TypeError' }); - // Lower and upper limits. - assert(!c.setMaxSendFragment(511)); - assert(!c.setMaxSendFragment(16385)); + // OpenSSL enforces Node's documented fragment size range. BoringSSL accepts + // both out-of-range values and reports success, so assert that difference + // explicitly instead of using a truthiness shortcut. + const acceptsOutOfRangeFragmentSize = + process.features.openssl_is_boringssl; + assert.strictEqual(c.setMaxSendFragment(511), + acceptsOutOfRangeFragmentSize); + assert.strictEqual(c.setMaxSendFragment(16385), + acceptsOutOfRangeFragmentSize); // Correct fragment size. assert(c.setMaxSendFragment(maxChunk)); diff --git a/test/parallel/test-tls-min-max-version.js b/test/parallel/test-tls-min-max-version.js index 4903d92f5c5700..abddbbeb0eba1b 100644 --- a/test/parallel/test-tls-min-max-version.js +++ b/test/parallel/test-tls-min-max-version.js @@ -4,6 +4,12 @@ const common = require('../common'); if (!common.hasCrypto) { common.skip('missing crypto'); } + +if (process.features.openssl_is_boringssl) { + require('../common/boringssl').testLegacyProtocolUnsupported(); + return; +} + const { hasOpenSSL, hasOpenSSL3, diff --git a/test/parallel/test-tls-multi-key.js b/test/parallel/test-tls-multi-key.js index 89f9931e5bdd77..0a9c6f108bf675 100644 --- a/test/parallel/test-tls-multi-key.js +++ b/test/parallel/test-tls-multi-key.js @@ -27,6 +27,11 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); +if (process.features.openssl_is_boringssl) { + require('../common/boringssl').assertMultiKeyUnsupported(); + return; +} + const fixtures = require('../common/fixtures'); const assert = require('assert'); const tls = require('tls'); diff --git a/test/parallel/test-tls-multi-pfx.js b/test/parallel/test-tls-multi-pfx.js index 526b77b1484cd3..fec697cd3b7093 100644 --- a/test/parallel/test-tls-multi-pfx.js +++ b/test/parallel/test-tls-multi-pfx.js @@ -3,6 +3,11 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); +if (process.features.openssl_is_boringssl) { + require('../common/boringssl').testMultiPfxSelectionDifference(); + return; +} + const assert = require('assert'); const tls = require('tls'); const fixtures = require('../common/fixtures'); diff --git a/test/parallel/test-tls-no-cert-required.js b/test/parallel/test-tls-no-cert-required.js index b3dcfa516ab502..499ab2dfd14ed2 100644 --- a/test/parallel/test-tls-no-cert-required.js +++ b/test/parallel/test-tls-no-cert-required.js @@ -28,10 +28,15 @@ const assert = require('assert'); const tls = require('tls'); // Omitting the cert or pfx option to tls.createServer() should not throw. -// AECDH-NULL-SHA is a no-authentication/no-encryption cipher and hence -// doesn't need a certificate. -tls.createServer({ ciphers: 'AECDH-NULL-SHA' }) - .listen(0, common.mustCall(close)); +if (process.features.openssl_is_boringssl) { + // AECDH-NULL-SHA is a no-authentication/no-encryption cipher and hence + // does not need a certificate. BoringSSL does not provide that anonymous + // cipher suite, so only this cipher-specific no-cert case is skipped. + common.printSkipMessage('BoringSSL: skipping anonymous AECDH-NULL-SHA case'); +} else { + tls.createServer({ ciphers: 'AECDH-NULL-SHA' }) + .listen(0, common.mustCall(close)); +} tls.createServer(assert.fail) .listen(0, common.mustCall(close)); diff --git a/test/parallel/test-tls-options-boolean-check.js b/test/parallel/test-tls-options-boolean-check.js index 900a39f0c1cd42..f7dd7bb102f361 100644 --- a/test/parallel/test-tls-options-boolean-check.js +++ b/test/parallel/test-tls-options-boolean-check.js @@ -40,9 +40,23 @@ const keyDataView = toDataView(keyBuff); const certDataView = toDataView(certBuff); const caArrDataView = toDataView(caCert); +function filterBoringSSLKeyCertArrayCases(options, setName) { + if (!process.features.openssl_is_boringssl) + return options; + + // The array-valued cases exercise multi-identity key/cert handling. + // BoringSSL may reject those cases with backend key/cert mismatch errors + // before the boolean/type validation this test is targeting. Keep the scalar + // cases so tls.createServer() option type validation is still covered. + common.printSkipMessage( + `BoringSSL: skipping ${setName} key/cert array cases`); + return options.filter(([key, cert]) => !Array.isArray(key) && + !Array.isArray(cert)); +} + // Checks to ensure tls.createServer doesn't throw an error // Format ['key', 'cert'] -[ +const validOptions = [ [keyBuff, certBuff], [false, certBuff], [keyBuff, false], @@ -62,13 +76,16 @@ const caArrDataView = toDataView(caCert); [false, [certStr, certStr2]], [[{ pem: keyBuff }], false], [[{ pem: keyBuff }, { pem: keyBuff }], false], -].forEach(([key, cert]) => { - tls.createServer({ key, cert }); -}); +]; + +filterBoringSSLKeyCertArrayCases(validOptions, 'valid') + .forEach(([key, cert]) => { + tls.createServer({ key, cert }); + }); // Checks to ensure tls.createServer predictably throws an error // Format ['key', 'cert', 'expected message'] -[ +const invalidKeyOptions = [ [true, certBuff], [true, certStr], [true, certArrBuff], @@ -80,7 +97,10 @@ const caArrDataView = toDataView(caCert); [[true, keyStr2], [certStr, certStr2], 0], [[true, false], [certBuff, certBuff2], 0], [true, [certBuff, certBuff2]], -].forEach(([key, cert, index]) => { +]; + +for (const [key, cert, index] of + filterBoringSSLKeyCertArrayCases(invalidKeyOptions, 'invalid key')) { const val = index === undefined ? key : key[index]; assert.throws(() => { tls.createServer({ key, cert }); @@ -91,9 +111,9 @@ const caArrDataView = toDataView(caCert); 'instance of Buffer, TypedArray, or DataView.' + common.invalidArgTypeHelper(val) }); -}); +} -[ +const invalidCertOptions = [ [keyBuff, true], [keyStr, true], [keyArrBuff, true], @@ -106,7 +126,10 @@ const caArrDataView = toDataView(caCert); [[keyStr, keyStr2], [certStr, true], 1], [[keyStr, keyStr2], [true, false], 0], [[keyStr, keyStr2], true], -].forEach(([key, cert, index]) => { +]; + +for (const [key, cert, index] of + filterBoringSSLKeyCertArrayCases(invalidCertOptions, 'invalid cert')) { const val = index === undefined ? cert : cert[index]; assert.throws(() => { tls.createServer({ key, cert }); @@ -117,7 +140,7 @@ const caArrDataView = toDataView(caCert); 'instance of Buffer, TypedArray, or DataView.' + common.invalidArgTypeHelper(val) }); -}); +} // Checks to ensure tls.createServer works with the CA parameter // Format ['key', 'cert', 'ca'] diff --git a/test/parallel/test-tls-passphrase.js b/test/parallel/test-tls-passphrase.js index 8d802400f6ee3b..4372da249bb509 100644 --- a/test/parallel/test-tls-passphrase.js +++ b/test/parallel/test-tls-passphrase.js @@ -223,7 +223,7 @@ server.listen(0, common.mustCall(function() { }, onSecureConnect()); })).unref(); -const errMessageDecrypt = /bad decrypt/; +const errMessageDecrypt = /bad[ _]decrypt/i; // Missing passphrase assert.throws(function() { diff --git a/test/parallel/test-tls-psk-alpn-callback-exception-handling.js b/test/parallel/test-tls-psk-alpn-callback-exception-handling.js index 881215672ecd0d..cdeb9f3b31f8fe 100644 --- a/test/parallel/test-tls-psk-alpn-callback-exception-handling.js +++ b/test/parallel/test-tls-psk-alpn-callback-exception-handling.js @@ -14,6 +14,11 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); +if (process.features.openssl_is_boringssl) { + require('../common/boringssl').testPskTls13Unsupported(); + return; +} + const assert = require('assert'); const { describe, it } = require('node:test'); const tls = require('tls'); diff --git a/test/parallel/test-tls-psk-circuit.js b/test/parallel/test-tls-psk-circuit.js index bdf9c86c26a7b6..c9c93d53350165 100644 --- a/test/parallel/test-tls-psk-circuit.js +++ b/test/parallel/test-tls-psk-circuit.js @@ -5,6 +5,11 @@ if (!common.hasCrypto) { common.skip('missing crypto'); } +if (process.features.openssl_is_boringssl) { + require('../common/boringssl').testPskTls13Unsupported(); + return; +} + const { hasOpenSSL } = require('../common/crypto'); const assert = require('assert'); const tls = require('tls'); diff --git a/test/parallel/test-tls-psk-server.js b/test/parallel/test-tls-psk-server.js index af038493469880..692550fc1c198b 100644 --- a/test/parallel/test-tls-psk-server.js +++ b/test/parallel/test-tls-psk-server.js @@ -5,6 +5,11 @@ if (!common.hasCrypto) { common.skip('missing crypto'); } +if (process.features.openssl_is_boringssl) { + require('../common/boringssl').testPskTls13Unsupported(); + return; +} + const { opensslCli } = require('../common/crypto'); if (!opensslCli) { diff --git a/test/parallel/test-tls-reduced-SECLEVEL-in-cipher.js b/test/parallel/test-tls-reduced-SECLEVEL-in-cipher.js index 9f4458e0a7d671..cca22067a0fe19 100644 --- a/test/parallel/test-tls-reduced-SECLEVEL-in-cipher.js +++ b/test/parallel/test-tls-reduced-SECLEVEL-in-cipher.js @@ -4,6 +4,11 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); +if (process.features.openssl_is_boringssl) { + require('../common/boringssl').assertOpenSSLSecurityLevelsUnsupported(); + return; +} + const assert = require('assert'); const tls = require('tls'); const fixtures = require('../common/fixtures'); diff --git a/test/parallel/test-tls-server-failed-handshake-emits-clienterror.js b/test/parallel/test-tls-server-failed-handshake-emits-clienterror.js index 2fb43b9cbbf87a..9c30989af0afb3 100644 --- a/test/parallel/test-tls-server-failed-handshake-emits-clienterror.js +++ b/test/parallel/test-tls-server-failed-handshake-emits-clienterror.js @@ -22,7 +22,7 @@ const server = tls.createServer({}) 'Instance of Error should be passed to error handler'); assert.match( e.message, - /SSL routines:[^:]*:wrong version number/, + /SSL routines:[^:]*:wrong[ _]version[ _]number/i, ); server.close(); diff --git a/test/parallel/test-tls-server-verify.js b/test/parallel/test-tls-server-verify.js index 94f372d37a3b1f..439e321310305a 100644 --- a/test/parallel/test-tls-server-verify.js +++ b/test/parallel/test-tls-server-verify.js @@ -47,7 +47,7 @@ const { SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION } = const tls = require('tls'); const fixtures = require('../common/fixtures'); -const testCases = +let testCases = [{ title: 'Do not request certs. Everyone is unauthorized.', requestCert: false, rejectUnauthorized: false, @@ -125,6 +125,15 @@ const testCases = ] }, ]; +if (process.features.openssl_is_boringssl) { + // Remove the delayed client-certificate verification case. It depends on TLS + // renegotiation to request a client certificate after the initial handshake, + // but BoringSSL does not support caller-initiated renegotiation. + common.printSkipMessage( + 'BoringSSL: skipping renegotiated client certificate verification case'); + testCases = testCases.filter((tcase) => !tcase.renegotiate); +} + function filenamePEM(n) { return fixtures.path('keys', `${n}.pem`); } diff --git a/test/parallel/test-tls-session-cache.js b/test/parallel/test-tls-session-cache.js index aaf9c2c03c83e9..ae560e567980c9 100644 --- a/test/parallel/test-tls-session-cache.js +++ b/test/parallel/test-tls-session-cache.js @@ -37,6 +37,7 @@ const fixtures = require('../common/fixtures'); const assert = require('assert'); const tls = require('tls'); const { spawn } = require('child_process'); +const isBoringSSL = process.features.openssl_is_boringssl; doTest({ tickets: false }, function() { doTest({ tickets: true }, function() { @@ -56,7 +57,9 @@ function doTest(testOptions, callback) { requestCert: true, rejectUnauthorized: false, secureProtocol: 'TLS_method', - ciphers: 'RSA@SECLEVEL=0' + // BoringSSL supports the RSA cipher selector, but not OpenSSL's + // cipher-string policy command syntax. + ciphers: isBoringSSL ? 'RSA' : 'RSA@SECLEVEL=0' }; let requestCount = 0; let resumeCount = 0; @@ -105,7 +108,7 @@ function doTest(testOptions, callback) { server.listen(0, common.mustCall(function() { const args = [ 's_client', - '-tls1', + isBoringSSL ? '-tls1_2' : '-tls1', '-cipher', (hasOpenSSL(3, 1) ? 'DEFAULT:@SECLEVEL=0' : 'DEFAULT'), '-connect', `localhost:${this.address().port}`, '-servername', 'ohgod', diff --git a/test/parallel/test-tls-set-ciphers-error.js b/test/parallel/test-tls-set-ciphers-error.js index 3cfc8c391bf7d5..b79bd512ffe1db 100644 --- a/test/parallel/test-tls-set-ciphers-error.js +++ b/test/parallel/test-tls-set-ciphers-error.js @@ -21,8 +21,12 @@ const { hasOpenSSL } = require('../common/crypto'); assert.throws(() => tls.createServer(options, common.mustNotCall()), /no[_ ]cipher[_ ]match/i); options.ciphers = 'TLS_not_a_cipher'; - assert.throws(() => tls.createServer(options, common.mustNotCall()), - /no[_ ]cipher[_ ]match/i); + if (process.features.openssl_is_boringssl) { + tls.createServer(options).close(); + } else { + assert.throws(() => tls.createServer(options, common.mustNotCall()), + /no[_ ]cipher[_ ]match/i); + } } // Cipher name matching is case-sensitive prior to OpenSSL 4.0, and diff --git a/test/parallel/test-tls-set-default-ca-certificates-recovery.js b/test/parallel/test-tls-set-default-ca-certificates-recovery.js index e3eb0e84149ae8..ea6f98d5686e03 100644 --- a/test/parallel/test-tls-set-default-ca-certificates-recovery.js +++ b/test/parallel/test-tls-set-default-ca-certificates-recovery.js @@ -27,7 +27,9 @@ function testRecovery(expectedCerts) { { const invalidCert = '-----BEGIN CERTIFICATE-----\nvalid cert content\n-----END CERTIFICATE-----'; assert.throws(() => tls.setDefaultCACertificates([fixtureCert, invalidCert]), { - code: 'ERR_OSSL_PEM_ASN1_LIB', + code: process.features.openssl_is_boringssl ? + 'ERR_OSSL_PEM_ASN.1_ENCODING_ROUTINES' : + 'ERR_OSSL_PEM_ASN1_LIB', }); assertEqualCerts(tls.getCACertificates('default'), expectedCerts); } diff --git a/test/parallel/test-tls-set-sigalgs.js b/test/parallel/test-tls-set-sigalgs.js index 1bce814f3e8604..e1bf8b93f8a342 100644 --- a/test/parallel/test-tls-set-sigalgs.js +++ b/test/parallel/test-tls-set-sigalgs.js @@ -39,9 +39,14 @@ function test(csigalgs, ssigalgs, shared_sigalgs, cerr, serr) { assert.ifError(pair.client.err); assert(pair.server.conn); assert(pair.client.conn); + // BoringSSL's OpenSSL-compatible SSL_get_shared_sigalgs() API always + // returns zero, so a successful handshake still reports an empty list. + const expectedSharedSigalgs = process.features.openssl_is_boringssl ? + [] : + shared_sigalgs; assert.deepStrictEqual( pair.server.conn.getSharedSigalgs(), - shared_sigalgs + expectedSharedSigalgs ); } else { if (serr) { @@ -69,10 +74,13 @@ test('RSA-PSS+SHA256:RSA-PSS+SHA512:ECDSA+SHA256', const handshakeErr = 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'; +const noSharedSigalgsErr = process.features.openssl_is_boringssl ? + 'ERR_SSL_NO_COMMON_SIGNATURE_ALGORITHMS' : + 'ERR_SSL_NO_SHARED_SIGNATURE_ALGORITHMS'; test('RSA-PSS+SHA384', 'ECDSA+SHA256', undefined, handshakeErr, - 'ERR_SSL_NO_SHARED_SIGNATURE_ALGORITHMS'); + noSharedSigalgsErr); test('RSA-PSS+SHA384:ECDSA+SHA256', 'ECDSA+SHA384:RSA-PSS+SHA256', undefined, handshakeErr, - 'ERR_SSL_NO_SHARED_SIGNATURE_ALGORITHMS'); + noSharedSigalgsErr); diff --git a/test/parallel/test-tls-socket-failed-handshake-emits-error.js b/test/parallel/test-tls-socket-failed-handshake-emits-error.js index c88f0c3a1855f2..c64d4ad4aabe8d 100644 --- a/test/parallel/test-tls-socket-failed-handshake-emits-error.js +++ b/test/parallel/test-tls-socket-failed-handshake-emits-error.js @@ -22,7 +22,7 @@ const server = net.createServer(common.mustCall((c) => { 'Instance of Error should be passed to error handler'); assert.match( e.message, - /SSL routines:[^:]*:wrong version number/, + /SSL routines:[^:]*:wrong[ _]version[ _]number/i, ); })); diff --git a/test/parallel/test-tls-ticket-cluster.js b/test/parallel/test-tls-ticket-cluster.js index 2ed4abb93c8d47..f183b53f24c0b9 100644 --- a/test/parallel/test-tls-ticket-cluster.js +++ b/test/parallel/test-tls-ticket-cluster.js @@ -24,6 +24,11 @@ const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); +if (process.features.openssl_is_boringssl) { + require('../common/boringssl').testTls13SessionTicketSemanticsDiffer(); + return; +} + const assert = require('assert'); const tls = require('tls'); const cluster = require('cluster'); diff --git a/test/parallel/test-tls-ticket.js b/test/parallel/test-tls-ticket.js index 0a77e52fb275cd..8316f5e8da8d8f 100644 --- a/test/parallel/test-tls-ticket.js +++ b/test/parallel/test-tls-ticket.js @@ -30,6 +30,12 @@ const net = require('net'); const crypto = require('crypto'); const fixtures = require('../common/fixtures'); +if (process.features.openssl_is_boringssl && + tls.DEFAULT_MAX_VERSION !== 'TLSv1.2') { + require('../common/boringssl').testTls13SessionTicketSemanticsDiffer(); + return; +} + const keys = crypto.randomBytes(48); const serverLog = []; const ticketLog = []; diff --git a/test/parallel/test-x509-escaping.js b/test/parallel/test-x509-escaping.js index a5937a09cb1535..ab91e334555669 100644 --- a/test/parallel/test-x509-escaping.js +++ b/test/parallel/test-x509-escaping.js @@ -438,7 +438,9 @@ const { hasOpenSSL3 } = require('../common/crypto'); const cert = fixtures.readKey('incorrect_san_correct_subject-cert.pem'); // The hostname is the CN, but not a SAN entry. - const servername = process.features.openssl_is_boringssl ? undefined : 'good.example.com'; + const servername = 'good.example.com'; + const cnFallback = process.features.openssl_is_boringssl ? undefined : + servername; const certX509 = new X509Certificate(cert); assert.strictEqual(certX509.subject, `CN=${servername}`); assert.strictEqual(certX509.subjectAltName, 'DNS:evil.example.com'); @@ -448,7 +450,7 @@ const { hasOpenSSL3 } = require('../common/crypto'); assert.strictEqual(certX509.checkHost(servername, { subject: 'default' }), undefined); assert.strictEqual(certX509.checkHost(servername, { subject: 'always' }), - servername); + cnFallback); assert.strictEqual(certX509.checkHost(servername, { subject: 'never' }), undefined); @@ -483,11 +485,13 @@ const { hasOpenSSL3 } = require('../common/crypto'); assert.strictEqual(certX509.subjectAltName, 'IP Address:1.2.3.4'); // The newer X509Certificate API allows customizing this behavior: - assert.strictEqual(certX509.checkHost(servername), servername); + const cnFallback = process.features.openssl_is_boringssl ? undefined : + servername; + assert.strictEqual(certX509.checkHost(servername), cnFallback); assert.strictEqual(certX509.checkHost(servername, { subject: 'default' }), - servername); + cnFallback); assert.strictEqual(certX509.checkHost(servername, { subject: 'always' }), - servername); + cnFallback); assert.strictEqual(certX509.checkHost(servername, { subject: 'never' }), undefined); diff --git a/test/sequential/test-tls-connect.js b/test/sequential/test-tls-connect.js index 189b9afa6352bb..ca8a1d8128554e 100644 --- a/test/sequential/test-tls-connect.js +++ b/test/sequential/test-tls-connect.js @@ -57,5 +57,5 @@ const tls = require('tls'); port: common.PORT, ciphers: 'rick-128-roll', }, common.mustNotCall()); - }, /no cipher match/i); + }, /no[_ ]cipher[_ ]match/i); } diff --git a/test/sequential/test-tls-psk-client.js b/test/sequential/test-tls-psk-client.js index 65e628a6f4e0eb..2eb6228f79f265 100644 --- a/test/sequential/test-tls-psk-client.js +++ b/test/sequential/test-tls-psk-client.js @@ -5,6 +5,11 @@ if (!common.hasCrypto) { common.skip('missing crypto'); } +if (process.features.openssl_is_boringssl) { + require('../common/boringssl').testPskTls13Unsupported(); + return; +} + const { opensslCli } = require('../common/crypto'); if (!opensslCli) {