From ec951bf36bc473efe00b83553285bd6af415e668 Mon Sep 17 00:00:00 2001 From: 2rdzy <232089760+2rdzy@users.noreply.github.com> Date: Mon, 5 Oct 2026 22:14:05 +0200 Subject: [PATCH 1/2] Add a test harness, CI and current package metadata Add node:test tests against a small in-process Electrum server over TCP and TLS (with a committed, test-only certificate), covering requests, errors, concurrency, batches, notifications and close. Run them in CI on Node 18, 20 and 22. Point the repository, bugs and homepage fields at this fork, require Node 18 or later, and publish only index.js and lib. --- .github/workflows/test.yml | 19 ++++++++++ package.json | 31 +++++++++++----- test/client.test.js | 67 +++++++++++++++++++++++++++++++++ test/fixtures/cert.pem | 19 ++++++++++ test/fixtures/key.pem | 28 ++++++++++++++ test/helpers/client.js | 23 ++++++++++++ test/helpers/server.js | 76 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 254 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100644 test/client.test.js create mode 100644 test/fixtures/cert.pem create mode 100644 test/fixtures/key.pem create mode 100644 test/helpers/client.js create mode 100644 test/helpers/server.js diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..9df8ef9 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,19 @@ +name: test + +on: + push: + branches: [master] + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + node: [18, 20, 22] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + - run: npm test diff --git a/package.json b/package.json index d6bf75e..7e2cdbe 100644 --- a/package.json +++ b/package.json @@ -4,16 +4,16 @@ "description": "Electrum protocol client for React Native & Node.js", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "node --test test/*.test.js" }, "dependencies": {}, "devDependencies": {}, "repository": { "type": "git", - "url": "git://github.com/janoside/electrum-client.git" + "url": "git+https://github.com/2rdzy/electrum-client.git" }, "bugs": { - "url": "https://github.com/janoside/electrum-client/issues" + "url": "https://github.com/2rdzy/electrum-client/issues" }, "keywords": [ "bitcoin", @@ -21,13 +21,26 @@ "electrumx" ], "engines": { - "node": ">=6" + "node": ">=18" }, "contributors": [ - { "name": "Yuki Akiyama" }, - { "name": "7kharov" }, - { "name": "overtorment" }, - { "name": "janoside" } + { + "name": "Yuki Akiyama" + }, + { + "name": "7kharov" + }, + { + "name": "overtorment" + }, + { + "name": "janoside" + } ], - "license": "MIT" + "license": "MIT", + "homepage": "https://github.com/2rdzy/electrum-client#readme", + "files": [ + "index.js", + "lib" + ] } diff --git a/test/client.test.js b/test/client.test.js new file mode 100644 index 0000000..36653c9 --- /dev/null +++ b/test/client.test.js @@ -0,0 +1,67 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const { after, before, describe, test } = require('node:test'); + +const { startServer } = require('./helpers/server.js'); +const { withClient } = require('./helpers/client.js'); + +const handler = request => { + switch (request.method) { + case 'server.version': return ['test-server 1.0', '1.4']; + case 'echo': return request.params; + case 'blockchain.scripthash.get_balance': return { confirmed: request.params[0].length, unconfirmed: 0 }; + case 'fail': throw { code: 2, message: 'nope' }; + default: throw { code: -32601, message: `unknown method ${request.method}` }; + } +}; + +for (const [protocol, useTls] of [['tcp', false], ['tls', true]]) { + describe(`over ${protocol}`, () => { + let server; + + before(async () => { server = await startServer({ useTls, handler }); }); + after(() => server.close()); + + test('negotiates the protocol version', () => withClient(server, protocol, client => { + assert.deepEqual(client.versionInfo, ['test-server 1.0', '1.4']); + })); + + test('resolves a request with its result', () => withClient(server, protocol, async client => { + assert.deepEqual(await client.request('echo', ['a', 1]), ['a', 1]); + })); + + test('rejects a request with the server error', () => withClient(server, protocol, async client => { + await assert.rejects(client.request('fail', []), { code: 2, message: 'nope' }); + })); + + test('keeps concurrent requests apart', () => withClient(server, protocol, async client => { + const results = await Promise.all([1, 2, 3, 4, 5].map(n => client.request('echo', [n]))); + + assert.deepEqual(results, [[1], [2], [3], [4], [5]]); + })); + + test('resolves a batch with one entry per parameter, tagged with it', () => withClient(server, protocol, async client => { + const results = await client.blockchainScripthash_getBalanceBatch(['aa', 'bbbb', 'cccccc']); + + assert.deepEqual(results.map(r => r.param), ['aa', 'bbbb', 'cccccc']); + assert.deepEqual(results.map(r => r.result.confirmed), [2, 4, 6]); + })); + + test('emits subscription notifications', () => withClient(server, protocol, async client => { + const notified = new Promise(resolve => client.subscribe.once('blockchain.headers.subscribe', resolve)); + + server.broadcast(JSON.stringify({ jsonrpc: '2.0', method: 'blockchain.headers.subscribe', params: [{ height: 7 }] })); + + assert.deepEqual(await notified, [{ height: 7 }]); + })); + + test('refuses requests after close', async () => { + await withClient(server, protocol, async client => { + client.close(); + + await assert.rejects(client.request('echo', []), /Connection to server lost/); + }); + }); + }); +} diff --git a/test/fixtures/cert.pem b/test/fixtures/cert.pem new file mode 100644 index 0000000..a2ae783 --- /dev/null +++ b/test/fixtures/cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDJzCCAg+gAwIBAgIUC8QQdK2UWC7Gzx1+oiCJs2ear6wwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MCAXDTI2MTAwNTIwMTMyNVoYDzIxMjYw +OTExMjAxMzI1WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQCpRxHcBqjb/VShru2qOjUGM+B9owDhiwKzM47xQ/iF +G7k0mtbFv9lHTL9NWTTEHy7prg461Z2XCP3QeNYYjV4udy9G2U9YuJuHHT+gx7br +Cr1Q6WT6kqc7RXe0R79GggqHpi02s3g/qbVO/OalBSvVBFpxxw9uMdG9Q7V5y7Jn +WI9NmYB/WRkBQS4dygw+wjS4igUF3j6vwR40gXm3QA66RxdSDNTOLnWLp6On92wj +DpmGNPciXCxhXquwW0o7ylRnQYsXpafHEMHl1uGjJQpXBuvgV/q1VmLFNKbp00T0 +YxiXV2gQA+IgnFMSQ7XKoKHPVJx1gPr97ERTiLYe/ci7AgMBAAGjbzBtMB0GA1Ud +DgQWBBT+7ktr7S5GLg39hICE95f36RZXzTAfBgNVHSMEGDAWgBT+7ktr7S5GLg39 +hICE95f36RZXzTAPBgNVHRMBAf8EBTADAQH/MBoGA1UdEQQTMBGCCWxvY2FsaG9z +dIcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEAhSP9Gxcxde1s8jfTStMn+VNmKGG3 +cWIVnw1IYf8PCTUx5z2gi1MjBpEV4QyL0tWNvfk0yfDCZJ24kXqLbgo338T7A5pQ +SfJJ1XMP/INRWtcsrnQZA4MdflD+9LzDjSaTNw8rLTQqemrRGLcDXfwmk+rAsGSJ +N20d/Txy1Ixlu7bBE95vMS5nHMK3P6XtlbZPGdDyZnN7JoomUYghWUkmtM00w1Ck +WPqNBCVsTRJZgyhgzvIXEBD7XcN4vMR6wWoTUhRxpW9QqlSQyzVr/Ik9DBh+QZ/n +DVdsD2+EMFtg0jzOJ9+fn+dMYpNJOjis8D1hAJZiku500/R2xjlmVdwYeg== +-----END CERTIFICATE----- diff --git a/test/fixtures/key.pem b/test/fixtures/key.pem new file mode 100644 index 0000000..03bc7b3 --- /dev/null +++ b/test/fixtures/key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCpRxHcBqjb/VSh +ru2qOjUGM+B9owDhiwKzM47xQ/iFG7k0mtbFv9lHTL9NWTTEHy7prg461Z2XCP3Q +eNYYjV4udy9G2U9YuJuHHT+gx7brCr1Q6WT6kqc7RXe0R79GggqHpi02s3g/qbVO +/OalBSvVBFpxxw9uMdG9Q7V5y7JnWI9NmYB/WRkBQS4dygw+wjS4igUF3j6vwR40 +gXm3QA66RxdSDNTOLnWLp6On92wjDpmGNPciXCxhXquwW0o7ylRnQYsXpafHEMHl +1uGjJQpXBuvgV/q1VmLFNKbp00T0YxiXV2gQA+IgnFMSQ7XKoKHPVJx1gPr97ERT +iLYe/ci7AgMBAAECggEABiIzx3aGT9JSFWRdQDnJChHUeeOaIzys5bQMjeQZ8S4b ++91QbZc8rD9fqdHS/k2S9EIA9KjLg67TuOYSpo5uHIjnsaLWI9oskyzAIsyZwtJ/ +dvBotEZJa6BiF1Tc3WqA2dbE9KJ0Ik+9PhYegAQ787AAsaFg3Gv5tD3r1B9JYO00 +I2nQ36zxw1YlOwHS5LBgYSePHlVP2BYo6M21C/JUs6oECL0DshNRkbE0qLv95trL +JHq0PRx/0ABwpcfgjEDftvA/kFsxDrP3HEnoHIdvGawO/wiURQQiIEEIwtGkMmTP +/7hXSUOf8XWrRk1+9ljxHpIhMEizbVgW0vjvQi9a4QKBgQDQSQhmzef9zM677qvs +xmjdP4S5XQMQXxCRXRYBVs15OURM+MRvWG+zhuhnMMI8tNe9jNCci3jpk5O5NKUN +Bn3P/SLGkr9odzD2wZ+9dw/KHpaCwuGqnngTGKgTaBWA0yyl6Kk59lUbABz0T9vN +87xREtUF2x752Vugpb7wZjt6RwKBgQDQDmr6fA/tUlrPxyb5QINXmFzNrv2WYCQn +L4mZi8fAaSUIb04+LGaKSEr8JhGKir5Vgy7OLQQT5AuPWeBkJcP324jddvJ03cdO +WSVGQ2r1pOjKz+N8egFVGCgi+YE21H2g6LbpXRH4vm/pLv1m7Agp+qy/mJkWe7kn +4GkVKStD7QKBgBhLLGuxQIndxY8t+2eWjMyhh/2G18ClFe2eid6GHVkyUMEZku/Z +xM2PwrOmgEFMZo15Syu3KNc1m1lSxp2KUEpeIBxBKYopvNBLLTKQHItanfuKd3/L +E/5GNfRtg3SPLPn89SAJl0ua68WjOuP+tcxPAgG42RgTPhiyvF6VzgIpAoGBAMJf +pewr+YlDNloFedDFR6+PRaxXPsy9NylwzQGpSE3nLVTq/9MwEgBAt+VH/31KT7V+ +ID04ayXWGEKtueh2K9YkwIH+vVcXVztIJ320QHoLvfy0KZhCN3pzXhQGu4QpEtFA +eJoefErCR7U3VEBSZPIrIjbBbsxFfF7X1fSSCLylAoGBAKZ652nLK3Tctg5F1PVm +PMO0K4Vs4rcXBURFq6vLB5QEs0pSC3kjYhrbT8cW6L8zThueFnGrK9SopGing/e8 +d6zhhkx5C+OzkDxbyirJKOzCtjrssVTEZId17dDPfr06YDXsD6+6pvFYxeDRxL5J +WhiL3lHrR0+npjrBWe9zJjpy +-----END PRIVATE KEY----- diff --git a/test/helpers/client.js b/test/helpers/client.js new file mode 100644 index 0000000..7b16b53 --- /dev/null +++ b/test/helpers/client.js @@ -0,0 +1,23 @@ +'use strict'; + +// The client expects net and tls to be injected as globals (see lib/client.js). +global.net = require('net'); +global.tls = require('tls'); + +const ElectrumClient = require('../../index.js'); + +// Connect a client to `server`, run `fn(client)`, and always close the client. +async function withClient(server, protocol, fn, { options, callbacks } = {}) { + const client = new ElectrumClient(server.port, '127.0.0.1', protocol, options, callbacks); + + await client.initElectrum({ client: 'electrum-client-test', version: '1.4' }, { retryPeriod: 100000, maxRetry: 0, callback: () => {} }); + + try { + return await fn(client); + + } finally { + client.close(); + } +} + +module.exports = { ElectrumClient, withClient }; diff --git a/test/helpers/server.js b/test/helpers/server.js new file mode 100644 index 0000000..fb88787 --- /dev/null +++ b/test/helpers/server.js @@ -0,0 +1,76 @@ +'use strict'; + +// A minimal Electrum-protocol server for tests: newline-delimited JSON-RPC over +// TCP or TLS. `handler(request)` returns the result for a request, or throws +// `{code, message}` to send an error. + +const fs = require('fs'); +const net = require('net'); +const path = require('path'); +const tls = require('tls'); + +const fixtures = path.join(__dirname, '..', 'fixtures'); + +const tlsOptions = { + key: fs.readFileSync(path.join(fixtures, 'key.pem')), + cert: fs.readFileSync(path.join(fixtures, 'cert.pem')), +}; + +function reply(request, handler) { + try { + return { jsonrpc: '2.0', id: request.id, result: handler(request) }; + + } catch (err) { + return { jsonrpc: '2.0', id: request.id, error: { code: err.code || -1, message: err.message || String(err) } }; + } +} + +async function startServer({ useTls = false, handler = req => req.params } = {}) { + const sockets = new Set(); + const received = []; + + const onConnection = socket => { + sockets.add(socket); + socket.on('close', () => sockets.delete(socket)); + socket.on('error', () => {}); + socket.setEncoding('utf8'); + + let buffer = ''; + socket.on('data', chunk => { + buffer += chunk; + + let index; + while ((index = buffer.indexOf('\n')) !== -1) { + const line = buffer.slice(0, index); + buffer = buffer.slice(index + 1); + + const message = JSON.parse(line); + received.push(message); + + const out = Array.isArray(message) + ? message.map(request => reply(request, handler)) + : reply(message, handler); + + socket.write(JSON.stringify(out) + '\n'); + } + }); + }; + + const server = useTls ? tls.createServer(tlsOptions, onConnection) : net.createServer(onConnection); + + await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)); + + return { + port: server.address().port, + received, + sockets, + // send a raw line to every connected client (for notifications and malformed input) + broadcast: line => sockets.forEach(socket => socket.write(line + '\n')), + close: () => new Promise(resolve => { + sockets.forEach(socket => socket.destroy()); + server.close(resolve); + }), + }; +} + +module.exports = { startServer, tlsOptions }; From 21092bc191d2c3d468a4f3faabc8313d726ae0f6 Mon Sep 17 00:00:00 2001 From: 2rdzy <232089760+2rdzy@users.noreply.github.com> Date: Mon, 5 Oct 2026 22:26:15 +0200 Subject: [PATCH 2/2] Verify the server certificate by default, with ways to trust a private one TlsSocketWrapper hard-coded rejectUnauthorized: false, so any certificate was accepted and a network attacker could impersonate the server. The options argument that the constructor already takes was not used for TLS. Verify by default, and read TLS options from options.tls: ca, servername and other tls.connect() options, rejectUnauthorized: false to skip verification as before, and fingerprint256 to accept only the certificate with that SHA-256 fingerprint (suited to self-signed servers). This is a breaking change for servers with self-signed certificates, so the version is now 2.0.0; the README says how to upgrade. --- README.md | 31 ++++++++++++++++++++ lib/TlsSocketWrapper.js | 38 ++++++++++++++++++++++-- lib/client.js | 2 +- package.json | 2 +- test/helpers/client.js | 5 ++++ test/tls.test.js | 64 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 test/tls.test.js diff --git a/README.md b/README.md index d807872..d1e3b53 100644 --- a/README.md +++ b/README.md @@ -32,3 +32,34 @@ For nodejs it should be provided before usage: ```javascript global.net = require('net'); ``` + +## TLS (`tls` and `ssl` protocols) + +`tls` should be provided as well (`global.tls = require('tls')` in node). + +**Since 2.0.0 the server certificate is verified by default.** Pass TLS options in `options.tls` (the fourth constructor argument): + +```javascript +const client = new ElectrumClient(50002, 'electrum.example.com', 'tls', { + tls: { + // trust a private certificate authority, or the server's own certificate (PEM) + ca: fs.readFileSync('server-cert.pem'), + + // or accept only the one certificate with this SHA-256 fingerprint (colons and case do not matter). + // This replaces chain and host name checks, so it suits self-signed servers. + fingerprint256: 'AB:CD:...', + + // or, for the old behaviour, skip verification. Anyone on the network path can then + // impersonate the server and read or alter what you send. + rejectUnauthorized: false + } +}); +``` + +`servername` sets the name sent for SNI and checked against the certificate. Any other key is passed to node's `tls.connect()`. + +To pin a self-signed certificate, get its fingerprint with `openssl x509 -in server-cert.pem -noout -fingerprint -sha256`. + +### Upgrading from 1.x + +1.x accepted any certificate. If you connect to a server with a self-signed certificate, add `ca` or `fingerprint256`, or set `rejectUnauthorized: false` to keep the old behaviour. diff --git a/lib/TlsSocketWrapper.js b/lib/TlsSocketWrapper.js index 13c373f..ab0a19c 100644 --- a/lib/TlsSocketWrapper.js +++ b/lib/TlsSocketWrapper.js @@ -3,9 +3,23 @@ * We implement several methods that TCP sockets are expected to have. We will proxy call them as soon as * real TLS socket will be created (TLS socket created after connection). */ +function normalizeFingerprint(fingerprint) { + return String(fingerprint).replace(/:/g, '').toLowerCase(); +} + class TlsSocketWrapper { - constructor(tls) { + /** + * tlsOptions (all optional): + * rejectUnauthorized verify the server certificate (default true) + * ca certificate authority (PEM string/Buffer, or an array) to trust in addition to the defaults + * fingerprint256 accept only the certificate with this SHA-256 fingerprint (hex, colons optional); + * when set, it replaces chain and host name verification, so it suits self-signed servers + * servername name to send for SNI and to check the certificate against + * Any other key is passed through to tls.connect(). + */ + constructor(tls, tlsOptions) { this._tls = tls; // dependency injection lol + this._tlsOptions = tlsOptions || {}; this._socket = false; // defaults: this._timeout = 5000; @@ -68,7 +82,27 @@ class TlsSocketWrapper { connect(port, host, callback) { // resulting TLSSocket extends - this._socket = this._tls.connect({ port: port, host: host, rejectUnauthorized: false }, () => { + const { fingerprint256, ...tlsOptions } = this._tlsOptions; + const pin = fingerprint256 ? normalizeFingerprint(fingerprint256) : null; + + this._socket = this._tls.connect({ + rejectUnauthorized: true, + ...tlsOptions, + // a pinned certificate is trusted by its fingerprint alone + ...(pin ? { rejectUnauthorized: false } : {}), + port: port, + host: host + }, () => { + if (pin) { + const cert = this._socket.getPeerCertificate(); + const actual = cert && cert.fingerprint256 ? normalizeFingerprint(cert.fingerprint256) : null; + + if (actual !== pin) { + this._socket.destroy(new Error('Server certificate does not match the pinned fingerprint')); + return; + } + } + return callback(); }); diff --git a/lib/client.js b/lib/client.js index 7900023..3787fff 100644 --- a/lib/client.js +++ b/lib/client.js @@ -49,7 +49,7 @@ class Client { throw new Error("Package 'tls' not available"); } - this.conn = new TlsSocketWrapper(tls); + this.conn = new TlsSocketWrapper(tls, options && options.tls); break; default: diff --git a/package.json b/package.json index 7e2cdbe..d8545de 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "electrum-client", - "version": "1.1.7", + "version": "2.0.0", "description": "Electrum protocol client for React Native & Node.js", "main": "index.js", "scripts": { diff --git a/test/helpers/client.js b/test/helpers/client.js index 7b16b53..5f2915f 100644 --- a/test/helpers/client.js +++ b/test/helpers/client.js @@ -8,6 +8,11 @@ const ElectrumClient = require('../../index.js'); // Connect a client to `server`, run `fn(client)`, and always close the client. async function withClient(server, protocol, fn, { options, callbacks } = {}) { + // the test server's certificate is self-signed: trust it unless a test says otherwise + if (protocol === 'tls' && !(options && options.tls)) { + options = { ...options, tls: { rejectUnauthorized: false } }; + } + const client = new ElectrumClient(server.port, '127.0.0.1', protocol, options, callbacks); await client.initElectrum({ client: 'electrum-client-test', version: '1.4' }, { retryPeriod: 100000, maxRetry: 0, callback: () => {} }); diff --git a/test/tls.test.js b/test/tls.test.js new file mode 100644 index 0000000..0e9d785 --- /dev/null +++ b/test/tls.test.js @@ -0,0 +1,64 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const crypto = require('node:crypto'); +const fs = require('node:fs'); +const path = require('node:path'); +const { after, before, describe, test } = require('node:test'); + +const { startServer } = require('./helpers/server.js'); +const { ElectrumClient } = require('./helpers/client.js'); + +const certPem = fs.readFileSync(path.join(__dirname, 'fixtures', 'cert.pem')); +const fingerprint = new crypto.X509Certificate(certPem).fingerprint256; // AA:BB:... + +const initConfig = { client: 'electrum-client-test', version: '1.4' }; +const noRetry = { retryPeriod: 10, maxRetry: 0, callback: () => {} }; + +describe('TLS verification', () => { + let server; + + before(async () => { server = await startServer({ useTls: true, handler: r => r.method === 'server.version' ? ['t', '1.4'] : r.params }); }); + after(() => server.close()); + + async function connect(tlsOptions) { + const client = new ElectrumClient(server.port, '127.0.0.1', 'tls', { tls: tlsOptions, connectTimeout: 2000 }, { onError: () => {} }); + + try { + await client.initElectrum(initConfig, noRetry); + + return await client.request('echo', ['hello']); + + } finally { + client.close(); + } + } + + test('rejects a self-signed certificate by default', async () => { + await assert.rejects(connect(undefined), /self.signed|unable to verify/i); + }); + + test('accepts it when its certificate is given as a trusted ca', async () => { + assert.deepEqual(await connect({ ca: certPem }), ['hello']); + }); + + test('accepts any certificate when verification is switched off', async () => { + assert.deepEqual(await connect({ rejectUnauthorized: false }), ['hello']); + }); + + test('accepts a certificate with the pinned fingerprint (colons and case do not matter)', async () => { + assert.deepEqual(await connect({ fingerprint256: fingerprint }), ['hello']); + assert.deepEqual(await connect({ fingerprint256: fingerprint.replace(/:/g, '').toLowerCase() }), ['hello']); + }); + + test('rejects a certificate with another fingerprint, even with verification off', async () => { + const other = fingerprint.replace(/^../, fingerprint.startsWith('00') ? '01' : '00'); + + await assert.rejects(connect({ fingerprint256: other }), /pinned fingerprint/); + await assert.rejects(connect({ fingerprint256: other, rejectUnauthorized: false }), /pinned fingerprint/); + }); + + test('rejects a trusted certificate that is for another host name', async () => { + await assert.rejects(connect({ ca: certPem, servername: 'not-the-server.example' }), /altnames|hostname|not in the cert/i); + }); +});