Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 56 additions & 4 deletions core/binkp/caller.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
'use strict';

const net = require('net');
const tls = require('tls');
const fsp = require('fs/promises');
const os = require('os');
const _ = require('lodash');

Expand Down Expand Up @@ -49,14 +51,18 @@
const host = nodeConf.host;
const port = nodeConf.port || 24554;

// Validate + build TLS options before acquiring the lock so a bad config
// fails fast without leaving a dangling .bsy file.
const tlsOpts = await _buildTlsOpts(nodeConf);

const locked = await spool.acquireLock(addr).catch(() => false);
if (!locked) {
Log.info({ addr: addrStr }, '[BinkP/Caller] Node already in session, skipping');
return;
}

try {
const socket = await _connect(host, port);
const socket = await _connect(host, port, tlsOpts);

const session = new BinkpSession(socket, {
role: 'originating',
Expand Down Expand Up @@ -187,16 +193,62 @@

// ── Private ───────────────────────────────────────────────────────────────────

function _connect(host, port) {
// Build a tls.connect() options object from the per-node config.
// Returns null when TLS is not requested.
// Throws with a descriptive message when TLS is enabled but no trust option
// is set — we refuse to silently open a connection that looks secure but
// provides no protection against MITM.
async function _buildTlsOpts(nodeConf) {
if (!nodeConf.tls) return null;

const hasTrust =
nodeConf.tlsAllowSelfSigned || nodeConf.tlsFingerprint || nodeConf.tlsCertFile;
if (!hasTrust) {
throw new Error(
`TLS enabled for ${nodeConf.host} but no trust option configured. ` +
`Set one of: tlsAllowSelfSigned, tlsFingerprint, or tlsCertFile.`
);
}

const opts = {};

if (nodeConf.tlsAllowSelfSigned) {
opts.rejectUnauthorized = false;

Check failure

Code scanning / CodeQL

Disabling certificate validation High

Disabling certificate validation is strongly discouraged.
Comment thread
NuSkooler marked this conversation as resolved.
Dismissed
opts.checkServerIdentity = () => {};
} else if (nodeConf.tlsFingerprint) {
const expected = nodeConf.tlsFingerprint;
opts.rejectUnauthorized = false; // we enforce trust via fingerprint

Check failure

Code scanning / CodeQL

Disabling certificate validation High

Disabling certificate validation is strongly discouraged.
Comment thread
NuSkooler marked this conversation as resolved.
Dismissed
opts.checkServerIdentity = (hostname, cert) => {
const got = cert.fingerprint256 || cert.fingerprint;
if (got !== expected) {
return new Error(
`TLS fingerprint mismatch for ${hostname}: expected ${expected}, got ${got}`
);
}
};
} else {
// tlsCertFile: trust a specific CA / self-signed cert
opts.ca = await fsp.readFile(nodeConf.tlsCertFile);
}

return opts;
}

function _connect(host, port, tlsOpts) {
return new Promise((resolve, reject) => {
const socket = net.createConnection({ host, port });
const socket = tlsOpts
? tls.connect({ ...tlsOpts, host, port })
: net.createConnection({ host, port });

// TLS handshake completes on 'secureConnect'; plain TCP uses 'connect'.
const connectEvent = tlsOpts ? 'secureConnect' : 'connect';

const timer = setTimeout(() => {
socket.destroy();
reject(new Error(`Connection to ${host}:${port} timed out`));
}, CONNECT_TIMEOUT_MS);

socket.once('connect', () => {
socket.once(connectEvent, () => {
clearTimeout(timer);
resolve(socket);
});
Expand Down
Loading
Loading