One bus: the runtime pins the certificate after the server speaks, and the old transport goes
Every module that dialled the new bus failed its handshake with "wrong version number": the runtime pinned the server's certificate by a raw TLS connection to a port on which the server speaks first, in the clear. The pin is taken after the INFO line now, on the same socket, and then the real connection verifies against exactly that certificate. And the old transport is deleted — its client, its tests, its dependency — with the wire-compatibility pins that only existed for the move (novox/hq ADR 0131, design 28 task 5.5). A credential names the bus, and there is one.
This commit is contained in:
+26
-9
@@ -17,6 +17,7 @@
|
||||
// correct.
|
||||
|
||||
import { createHash } from "node:crypto";
|
||||
import net from "node:net";
|
||||
import tls from "node:tls";
|
||||
import { connect as natsConnect, headers as natsHeaders, StringCodec, type JsMsg, type Subscription } from "nats";
|
||||
import type { Broker, Envelope, EventHeaders } from "@novox/mesh-sdk/messaging";
|
||||
@@ -307,16 +308,32 @@ function normalizeFingerprint(fingerprint: string): string {
|
||||
async function pinnedTls(rawUrl: string, fingerprint: string): Promise<{ ca: string }> {
|
||||
const url = new URL(rawUrl.includes("://") ? rawUrl : `nats://${rawUrl}`);
|
||||
const port = url.port ? Number(url.port) : 4222;
|
||||
// **The bus speaks first, in the clear.** A NATS server sends its INFO line before TLS begins,
|
||||
// and only then expects the client to start the handshake; a raw TLS connect to that port reads
|
||||
// the INFO line as a TLS record and fails with "wrong version number" — which is what every
|
||||
// module met the first time it dialled the bus being built (2026-09-28). So: connect, wait for
|
||||
// INFO, then start TLS on the same socket, and read the certificate the server presents.
|
||||
const certificate = await new Promise<tls.DetailedPeerCertificate>((resolve, reject) => {
|
||||
const socket = tls.connect(
|
||||
{ host: url.hostname, port, rejectUnauthorized: false, servername: url.hostname },
|
||||
() => {
|
||||
const peer = socket.getPeerCertificate(true);
|
||||
socket.end();
|
||||
resolve(peer);
|
||||
},
|
||||
);
|
||||
socket.on("error", reject);
|
||||
const plain = net.connect({ host: url.hostname, port }, () => {});
|
||||
let seenInfo = false;
|
||||
let buffered = "";
|
||||
plain.on("error", reject);
|
||||
plain.on("data", (chunk: Buffer) => {
|
||||
if (seenInfo) return;
|
||||
buffered += chunk.toString("utf8");
|
||||
if (!buffered.includes("\r\n")) return;
|
||||
seenInfo = true;
|
||||
plain.removeAllListeners("data");
|
||||
const secure = tls.connect(
|
||||
{ socket: plain, rejectUnauthorized: false, servername: url.hostname },
|
||||
() => {
|
||||
const peer = secure.getPeerCertificate(true);
|
||||
secure.end();
|
||||
resolve(peer);
|
||||
},
|
||||
);
|
||||
secure.on("error", reject);
|
||||
});
|
||||
});
|
||||
const seen = createHash("sha256").update(certificate.raw).digest("hex");
|
||||
if (seen !== normalizeFingerprint(fingerprint)) {
|
||||
|
||||
Reference in New Issue
Block a user