import assert from "node:assert/strict"; import { test } from "node:test"; import { readFileSync } from "node:fs"; import { spawnSync } from "node:child_process"; import { irohHost, knownHosts, sshArgs, ticket } from "../js/index.js"; import { atircTicket } from "./iroh.mjs"; const T = "endpointabex5k47heqqkdykv2b27ydg75jjaxeucm2ej74nt34a7up6wqlkyaiaenuhi5dqom5c6l3von3tcljrfzzgk3dbpexg4mbonfzg62bonruw42zof4"; test("ticket strips iroh:// and refuses non-tickets", () => { assert.equal(ticket(`iroh://${T}\n`), T); assert.equal(ticket(T), T); for (const bad of ["", "https://x", "iroh://ABC", "a b", null, "x".repeat(4097)]) assert.throws(() => ticket(bad)); }); test("knownHosts pins lexicon hostKeys and refuses none", () => { const keys = [ { type: "ssh-ed25519", publicKey: "ssh-ed25519 AAAAC3Nz root@guest", fingerprint: "SHA256:x" }, { type: "ssh-ed25519", publicKey: "AAAAC3Bare", fingerprint: "SHA256:y" }, // hat-7 shape "ecdsa-sha2-nistp256 AAAAE2 c", ]; assert.equal(knownHosts("guest", keys), "guest ssh-ed25519 AAAAC3Nz\nguest ssh-ed25519 AAAAC3Bare\nguest ecdsa-sha2-nistp256 AAAAE2\n"); assert.throws(() => knownHosts("guest", [])); assert.throws(() => knownHosts("guest", undefined)); }); test("sshArgs: an option-looking command stays the remote command (ssh -G, no network)", () => { const evil = "-oKnownHostsCommand=/nonexistent/pwned"; const args = sshArgs("/k/key", T, "/k/known_hosts", [evil]); assert.deepEqual(args.slice(-3), ["--", "root@guest", evil]); const g = spawnSync("ssh", ["-G", ...args], { encoding: "utf8" }); if (g.error) return; // no ssh binary here assert.match(g.stdout, new RegExp(`^proxycommand dumbpipe connect ${T}$`, "m")); assert.doesNotMatch(g.stdout, /pwned/); // without "--" ssh would take the command as an option: the regression this guards const bad = spawnSync("ssh", ["-G", ...args.filter((a) => a !== "--")], { encoding: "utf8" }); assert.match(bad.stdout, /pwned/); }); test("atircTicket reads the endpoint claim", () => { const jwt = ["e30", Buffer.from(JSON.stringify({ endpoint: T })).toString("base64url"), "sig"].join("."); assert.equal(atircTicket(jwt), T); }); test("irohHost: ssh with pinned host keys, or a raw exchange on a custom ALPN", async () => { const ran = []; const keys = [{ type: "ssh-ed25519", publicKey: "AAAAC3Bare" }]; let reply = { stdout: "fc-1\n", stderr: "", code: 0 }; const iroh = irohHost({ key: "/k/guest_ed25519", run: async (cmd, args, input) => { const known = args.find((a) => a.startsWith("UserKnownHostsFile=")); ran.push({ cmd, args, input, known: known && readFileSync(known.slice("UserKnownHostsFile=".length), "utf8") }); return reply; } }); assert.deepEqual(await iroh({ ticket: `iroh://${T}`, hostKeys: keys, command: "hostname" }), { output: "fc-1\n", exitCode: 0 }); assert.equal(ran[0].cmd, "ssh"); assert.ok(ran[0].args.includes(`ProxyCommand=dumbpipe connect ${T}`) && ran[0].args.includes("StrictHostKeyChecking=yes")); assert.deepEqual(ran[0].args.slice(-3), ["--", "root@guest", "hostname"]); assert.equal(ran[0].known, "guest ssh-ed25519 AAAAC3Bare\n"); assert.equal(ran[0].input, "", "nothing on stdin by default"); // a secret goes over the session's stdin, never into ssh's argv await iroh({ ticket: T, hostKeys: keys, command: "IFS= read -r t; echo ok", stdin: "secret-token\n" }); assert.equal(ran.at(-1).input, "secret-token\n"); assert.ok(!ran.at(-1).args.some((a) => a.includes("secret-token"))); await assert.rejects(iroh({ ticket: T, hostKeys: keys, command: "x", stdin: 5 }), /stdin must be a string/); await assert.rejects(iroh({ ticket: T, hostKeys: [], command: "x" }), /no host keys/); await assert.rejects(iroh({ ticket: T, hostKeys: keys }), /command is required/); await assert.rejects(iroh({ ticket: "nope!", hostKeys: keys, command: "x" }), /not an iroh ticket/); reply = { stdout: "", stderr: "Host key verification failed.", code: 255 }; await assert.rejects(iroh({ ticket: T, hostKeys: keys, command: "x" }), /Host key verification failed/); reply = { stdout: ":irc 001 me :hi\r\n", stderr: "", code: 0 }; assert.deepEqual(await iroh({ ticket: T, alpn: "atirc/irc/1", send: "NICK me\r\n" }), { output: ":irc 001 me :hi\r\n" }); assert.deepEqual(ran.at(-1), { cmd: "dumbpipe", args: ["connect", "--custom-alpn", "utf8:atirc/irc/1", T], input: "NICK me\r\n", known: undefined }); await assert.rejects(iroh({ ticket: T, alpn: "a b" }), /alpn/); });