summaryrefslogtreecommitdiffhomepage
path: root/packages/ssh/src/hostkey.test.ts
blob: 1870de0451c77703f0af2e5cc522ffc4d5d1ea3a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import { describe, expect, it } from "vitest";
import { decideHostKey, type HostKeyFingerprint, isKnownHost } from "./hostkey.js";

const fp = (token: string, key = "AAA"): HostKeyFingerprint => ({
  knownHostToken: token,
  keyBase64: key,
  keyType: "ssh-ed25519",
});

describe("decideHostKey — present + match → accept, no append", () => {
  it("accepts when the pinned key matches exactly", () => {
    const known = "myhost ssh-ed25519 AAA\n";
    const d = decideHostKey(known, fp("myhost", "AAA"));
    expect(d.accept).toBe(true);
    expect(d.append).toBeUndefined();
    expect(d.reason).toContain("matches");
  });

  it("matches ignoring leading/trailing whitespace differences", () => {
    const known = "myhost   ssh-ed25519   AAA\n";
    const d = decideHostKey(known, fp("myhost", "AAA"));
    expect(d.accept).toBe(true);
    expect(d.append).toBeUndefined();
  });

  it("matches a comma-host token list containing the alias", () => {
    const known = "hostA,myhost,hostB ssh-ed25519 AAA\n";
    const d = decideHostKey(known, fp("myhost", "AAA"));
    expect(d.accept).toBe(true);
  });
});

describe("decideHostKey — present + mismatch → REJECT, no append", () => {
  it("rejects loudly when the pinned key differs", () => {
    const known = "myhost ssh-ed25519 AAA\n";
    const d = decideHostKey(known, fp("myhost", "BBB"));
    expect(d.accept).toBe(false);
    expect(d.append).toBeUndefined(); // never pin a mismatched key
    expect(d.reason).toContain("HOST KEY CHANGED");
    expect(d.reason).toContain("myhost");
  });

  it("does not pin on mismatch (the user must clear the stale line)", () => {
    const known = "myhost ssh-ed25519 AAA\n";
    const d = decideHostKey(known, fp("myhost", "DIFFERENT"));
    expect(d.append).toBeUndefined();
  });
});

describe("decideHostKey — absent (first connect) → accept + pin", () => {
  it("accepts and produces the pin line to append", () => {
    const d = decideHostKey("", fp("newhost", "AAA"));
    expect(d.accept).toBe(true);
    expect(d.append).toBe("newhost ssh-ed25519 AAA");
    expect(d.reason).toContain("first connect");
    expect(d.reason).toContain("newhost");
  });

  it("ignores comment + empty lines when searching", () => {
    const known = "# a comment\n\n  \notherhost ssh-ed25519 ZZZ\n";
    const d = decideHostKey(known, fp("newhost", "AAA"));
    expect(d.accept).toBe(true);
    expect(d.append).toBe("newhost ssh-ed25519 AAA");
  });

  it("pins a bracketed token for a non-default port", () => {
    const d = decideHostKey("", fp("[localhost]:2222", "AAA"));
    expect(d.accept).toBe(true);
    expect(d.append).toBe("[localhost]:2222 ssh-ed25519 AAA");
  });
});

describe("decideHostKey — first field must match the token", () => {
  it("does not match a host that appears only as a substring of another token", () => {
    const known = "myhost-extra ssh-ed25519 AAA\n";
    const d = decideHostKey(known, fp("myhost", "AAA"));
    // "myhost" is not an exact first-field (nor comma element) → absent → pin.
    expect(d.accept).toBe(true);
    expect(d.append).toBe("myhost ssh-ed25519 AAA");
  });
});

describe("isKnownHost", () => {
  it("returns true when the token is a known_hosts first field", () => {
    expect(isKnownHost("a.example ssh-ed25519 AAA\n", "a.example")).toBe(true);
  });

  it("returns true for a comma-list token", () => {
    expect(isKnownHost("a,b,c ssh-ed25519 AAA\n", "b")).toBe(true);
  });

  it("returns false when the token is absent", () => {
    expect(isKnownHost("a.example ssh-ed25519 AAA\n", "b.example")).toBe(false);
  });

  it("returns false for an empty known_hosts", () => {
    expect(isKnownHost("", "anything")).toBe(false);
  });

  it("ignores comments and blanks", () => {
    const known = "# comment\n\nfoo ssh-ed25519 AAA\n";
    expect(isKnownHost(known, "foo")).toBe(true);
    expect(isKnownHost(known, "bar")).toBe(false);
  });
});