dsh-remote 0.8.18 → 0.8.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/hostkey.js CHANGED
@@ -3,6 +3,8 @@
3
3
  // ssh2 v1.17) is unit-tested against the REAL wire shape — the v0.6.7 bug
4
4
  // class ("mock object hid a contract drift") must not regress.
5
5
  import { createHash } from 'node:crypto'
6
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
7
+ import path from 'node:path'
6
8
 
7
9
  /**
8
10
  * Extract the SSH host-key algorithm name from a raw SSH host-key blob
@@ -34,6 +36,64 @@ export function keyFingerprint(key) {
34
36
  return createHash('sha256').update(blob).digest('base64')
35
37
  }
36
38
 
39
+ export function loadKnownHosts(file) {
40
+ if (!file) return {}
41
+ try {
42
+ const j = JSON.parse(readFileSync(file, 'utf8'))
43
+ if (j && typeof j === 'object' && !Array.isArray(j)) return j
44
+ } catch {}
45
+ return {}
46
+ }
47
+
48
+ export function isHostKeyKnown(file, host, port) {
49
+ return Object.prototype.hasOwnProperty.call(loadKnownHosts(file), `${host}:${port}`)
50
+ }
51
+
52
+ /** Build an ssh2 `hostVerifier` bound to the current config + known_hosts file. */
53
+ export function createHostKeyGuard(config, knownHostsFile) {
54
+ const id = () => `${config.host}:${config.port}`
55
+ const mode = config.hostKeyMode === 'verify' || config.hostKeyMode === 'off'
56
+ ? config.hostKeyMode
57
+ : 'accept-new'
58
+ const read = () => loadKnownHosts(knownHostsFile)
59
+ const write = (kh) => {
60
+ if (!knownHostsFile) return
61
+ try { mkdirSync(path.dirname(knownHostsFile), { recursive: true }) } catch {}
62
+ writeFileSync(knownHostsFile, JSON.stringify(kh, null, 2))
63
+ }
64
+ const guard = {
65
+ mode,
66
+ lastError: null,
67
+ knownHosts: read,
68
+ forgetHost() {
69
+ const kh = read()
70
+ delete kh[id()]
71
+ write(kh)
72
+ },
73
+ verifier(key) {
74
+ if (mode === 'off') return true
75
+ const fp = keyFingerprint(key)
76
+ const kh = read()
77
+ const stored = kh[id()]
78
+ if (stored) {
79
+ if (stored.fingerprint === fp) return true
80
+ guard.lastError =
81
+ `host key for ${id()} CHANGED (stored ${stored.fingerprint}, received ${fp}) — ` +
82
+ 'possible man-in-the-middle; run /remote-forget-key to re-trust if this is expected'
83
+ return false
84
+ }
85
+ if (mode === 'verify') {
86
+ guard.lastError = `unknown host key for ${id()} (hostKeyMode=verify) — trust it first with accept-new`
87
+ return false
88
+ }
89
+ kh[id()] = { algo: blobAlgorithm(key) || (key && key.algo) || 'unknown', fingerprint: fp, firstSeen: new Date().toISOString() }
90
+ write(kh)
91
+ return true
92
+ },
93
+ }
94
+ return guard
95
+ }
96
+
37
97
  /** Build a fake-but-wire-shaped host-key blob for tests:
38
98
  * `string(algo) string(32 bytes)`. */
39
99
  export function makeKeyBlob(algo, seed) {