mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-18 09:39:14 +00:00
106 lines
3.9 KiB
TypeScript
106 lines
3.9 KiB
TypeScript
import { describe, it, expect, vi } from "vitest";
|
|
import { generateKeyPairSync } from "crypto";
|
|
import ssh2Pkg, { type ParsedKey } from "ssh2";
|
|
import { MemoryAgent } from "../../src/backend/helpers.js";
|
|
|
|
describe("MemoryAgent", () => {
|
|
it("serves identities and signatures over the agent protocol", async () => {
|
|
const { privateKey } = generateKeyPairSync("rsa", { modulusLength: 2048 });
|
|
const parsed = ssh2Pkg.utils.parseKey(
|
|
privateKey.export({ type: "pkcs1", format: "pem" }),
|
|
);
|
|
expect(parsed).not.toBeInstanceOf(Error);
|
|
|
|
const agent = new MemoryAgent(parsed as ParsedKey);
|
|
const stream = await new Promise<NodeJS.ReadWriteStream>(
|
|
(resolve, reject) => {
|
|
agent.getStream((error, result) => {
|
|
if (error || !result)
|
|
reject(error ?? new Error("Missing agent stream"));
|
|
else resolve(result);
|
|
});
|
|
},
|
|
);
|
|
const client = new ssh2Pkg.AgentProtocol(true);
|
|
client.pipe(stream).pipe(client);
|
|
|
|
const identities = await new Promise<ParsedKey[]>((resolve, reject) => {
|
|
client.getIdentities((error, keys) => {
|
|
if (error || !keys) reject(error ?? new Error("Missing identities"));
|
|
else resolve(keys);
|
|
});
|
|
});
|
|
expect(identities).toHaveLength(1);
|
|
expect(identities[0].getPublicSSH()).toEqual(
|
|
(parsed as ParsedKey).getPublicSSH(),
|
|
);
|
|
|
|
const data = Buffer.from("forwarded-agent-test");
|
|
const signature = await new Promise<Buffer>((resolve, reject) => {
|
|
client.sign(identities[0], data, (error, result) => {
|
|
if (error || !result) reject(error ?? new Error("Missing signature"));
|
|
else resolve(result);
|
|
});
|
|
});
|
|
expect((parsed as ParsedKey).verify(data, signature)).toBe(true);
|
|
|
|
client.destroy();
|
|
(stream as unknown as { destroy: () => void }).destroy();
|
|
});
|
|
});
|
|
|
|
describe("MemoryAgent unsupported extension framing", () => {
|
|
it.each([false, true])(
|
|
"can enumerate keys after rejecting session-bind (fragmented=%s)",
|
|
async (fragmented) => {
|
|
const { privateKey } = generateKeyPairSync("rsa", {
|
|
modulusLength: 2048,
|
|
});
|
|
const key = ssh2Pkg.utils.parseKey(
|
|
privateKey.export({ type: "pkcs1", format: "pem" }),
|
|
) as ParsedKey;
|
|
const agent = new MemoryAgent(key);
|
|
const stream = await new Promise<NodeJS.ReadWriteStream>(
|
|
(resolve, reject) => {
|
|
agent.getStream((error, result) =>
|
|
error || !result ? reject(error) : resolve(result),
|
|
);
|
|
},
|
|
);
|
|
const replies: Buffer[] = [];
|
|
let buffered = Buffer.alloc(0);
|
|
stream.on("data", (data: Buffer) => {
|
|
buffered = Buffer.concat([buffered, data]);
|
|
while (
|
|
buffered.length >= 4 &&
|
|
buffered.length >= buffered.readUInt32BE(0) + 4
|
|
) {
|
|
const end = buffered.readUInt32BE(0) + 4;
|
|
replies.push(buffered.subarray(4, end));
|
|
buffered = buffered.subarray(end);
|
|
}
|
|
});
|
|
const extension = Buffer.from("session-bind@openssh.com");
|
|
const request = Buffer.alloc(4 + 1 + 4 + extension.length);
|
|
request.writeUInt32BE(request.length - 4, 0);
|
|
request[4] = 27;
|
|
request.writeUInt32BE(extension.length, 5);
|
|
extension.copy(request, 9);
|
|
const identities = Buffer.from([0, 0, 0, 1, 11]);
|
|
try {
|
|
const requests = Buffer.concat([request, identities]);
|
|
if (fragmented) {
|
|
stream.write(requests.subarray(0, 7));
|
|
stream.write(requests.subarray(7));
|
|
} else stream.write(requests);
|
|
await vi.waitFor(() => expect(replies).toHaveLength(2));
|
|
expect(replies[0][0]).toBe(5); // Unsupported extension remains refused.
|
|
expect(replies[1][0]).toBe(12); // The following identities request succeeds.
|
|
expect(replies[1].readUInt32BE(1)).toBe(1);
|
|
} finally {
|
|
(stream as unknown as { destroy: () => void }).destroy();
|
|
}
|
|
},
|
|
);
|
|
});
|