// node --test js/ssh-datachannel.test.mjs // connectSsh's wiring with a fake @microsoft/dev-tunnels-ssh (no npm deps): sshd's banner that // arrived before ssh took the channel is fed first, later channel bytes follow, exec output is // collected, and the host key fingerprint is OpenSSH's SHA256:. The real library ran // against OpenSSH (local sshd, and live on a guest VM). import assert from "node:assert/strict"; import { Buffer } from "node:buffer"; import { createHash } from "node:crypto"; import { test } from "node:test"; import { connectSsh } from "./ssh-datachannel.js"; const HOST_KEY = Buffer.from("host-key-blob"); function fakeSsh(seen) { class BaseStream { chunks = []; onData(data) { this.chunks.push(data.toString()) } onEnd() {} } class CommandRequestMessage {} class SshClientSession { #handler; onAuthenticating(handler) { this.#handler = handler } async connect(stream) { this.stream = seen.stream = stream; await stream.write(Buffer.from("SSH-2.0-client\r\n")) } async authenticateServer() { seen.atServerAuth = this.stream.chunks.join(""); const e = { publicKey: { keyAlgorithmName: "ssh-rsa", getPublicKeyBytes: async () => HOST_KEY } }; this.#handler(e); return !!(await e.authenticationPromise); } async authenticateClient(credentials) { seen.credentials = credentials; return true } async openChannel() { let onData, onClosed; return { onDataReceived: (f) => (onData = f), onClosed: (f) => (onClosed = f), adjustWindow: (n) => (seen.window = (seen.window ?? 0) + n), send: async (data) => (seen.stdin ??= []).push(data.toString()), request: async (req) => { seen.request = { ...req }; queueMicrotask(() => { onData(Buffer.from(`ran ${req.command}\n`)); onClosed() }); return true; }, }; } close() { seen.closed = true } } return { BaseStream, CommandRequestMessage, SshClientSession, SshSessionConfiguration: class {}, Buffer }; } test("connectSsh: banner first, then the channel; exec as user with the key; OpenSSH host key fingerprint", async () => { const seen = {}; const sent = []; const dc = { send: (d) => sent.push(Buffer.from(d).toString()), close() {} }; const banner = new TextEncoder().encode("SSH-2.0-OpenSSH_9.6\r\n").buffer; const session = await connectSsh(fakeSsh(seen), dc, { key: "pair" }, "root", [banner]); assert.equal(seen.atServerAuth, "SSH-2.0-OpenSSH_9.6\r\n"); assert.deepEqual(sent, ["SSH-2.0-client\r\n"]); assert.deepEqual(seen.credentials, { username: "root", publicKeys: [{ key: "pair" }] }); assert.deepEqual(session.hostKey, { type: "ssh-rsa", fingerprint: `SHA256:${createHash("sha256").update(HOST_KEY).digest("base64").replace(/=+$/, "")}` }); dc.onmessage({ data: new TextEncoder().encode("more").buffer }); assert.equal(seen.stream.chunks.join(""), "SSH-2.0-OpenSSH_9.6\r\nmore"); assert.equal(await session.exec("id"), "ran id\n"); assert.deepEqual(seen.request, { requestType: "exec", command: "id", wantReply: true }); assert.equal(seen.window, "ran id\n".length); assert.equal(seen.stdin, undefined, "no stdin, no EOF: as before"); // a secret goes to the command's stdin (then EOF), not into the exec request's command assert.equal(await session.exec("IFS= read -r t", "tok\n"), "ran IFS= read -r t\n"); assert.deepEqual(seen.stdin, ["tok\n", ""]); session.close(); assert.ok(seen.closed); });