Connection tree with interleaved folders and hosts, tabbed terminals grouped by host, a pinned local shell, TOFU host key verification, and credentials held by the OS keyring. Terminal bytes travel on a per-session MessagePort with credit-based flow control, so one session running `cat` on a large file cannot stall the others. Transports reconnect on their own with jittered backoff, keeping the scrollback and the tab while replacing only the connection beneath. 156 tests, including integration tests against a real sshd and a migration exercised against a database built in the old shape.
68 lines
2.5 KiB
TypeScript
68 lines
2.5 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { fingerprintOf, keyTypeOf } from '../src/main/session/hostKey'
|
|
|
|
/**
|
|
* A real ed25519 host key, with the fingerprint `ssh-keygen -lf` prints for it:
|
|
*
|
|
* $ ssh-keygen -lf host_key.pub
|
|
* 256 SHA256:ASmbFHszdZLp5YkSiJnqmGfAhpZKOZwQJnk3rLUo4Xo (ED25519)
|
|
*
|
|
* The expected value is pinned rather than computed so the test proves the
|
|
* *format*, not merely that the function is self-consistent. A fingerprint that
|
|
* does not match what ssh-keygen shows is one the user cannot check against
|
|
* anything, which defeats the point of showing it at all.
|
|
*/
|
|
const ED25519_PUBLIC_KEY =
|
|
'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKzewpDb5LqzDdJeN9GQVNBzIVE3ZPZ9cGWFpurXr1o8 test@host'
|
|
const ED25519_FINGERPRINT = 'SHA256:ASmbFHszdZLp5YkSiJnqmGfAhpZKOZwQJnk3rLUo4Xo'
|
|
|
|
function blobOf(publicKeyLine: string): Buffer {
|
|
const base64 = publicKeyLine.trim().split(/\s+/)[1]
|
|
if (!base64) throw new Error('not a public key line')
|
|
return Buffer.from(base64, 'base64')
|
|
}
|
|
|
|
describe('keyTypeOf', () => {
|
|
it('reads the algorithm name out of the wire format', () => {
|
|
expect(keyTypeOf(blobOf(ED25519_PUBLIC_KEY))).toBe('ssh-ed25519')
|
|
})
|
|
|
|
it('returns unknown rather than throwing on a malformed blob', () => {
|
|
// A short or bogus blob must not take down a connection attempt — it just
|
|
// cannot be identified, and the fingerprint check will still be shown.
|
|
expect(keyTypeOf(Buffer.alloc(0))).toBe('unknown')
|
|
expect(keyTypeOf(Buffer.from([0, 0]))).toBe('unknown')
|
|
// Declares a 4GB name.
|
|
expect(keyTypeOf(Buffer.from([0xff, 0xff, 0xff, 0xff, 1, 2, 3]))).toBe('unknown')
|
|
})
|
|
})
|
|
|
|
describe('fingerprintOf', () => {
|
|
it('matches the OpenSSH SHA256 fingerprint exactly', () => {
|
|
expect(fingerprintOf(blobOf(ED25519_PUBLIC_KEY))).toBe(ED25519_FINGERPRINT)
|
|
})
|
|
|
|
it('strips base64 padding, as OpenSSH does', () => {
|
|
expect(fingerprintOf(blobOf(ED25519_PUBLIC_KEY))).not.toContain('=')
|
|
})
|
|
|
|
it('is stable for the same key', () => {
|
|
const blob = blobOf(ED25519_PUBLIC_KEY)
|
|
expect(fingerprintOf(blob)).toBe(fingerprintOf(blob))
|
|
})
|
|
|
|
it('differs for a different key', () => {
|
|
const a = blobOf(ED25519_PUBLIC_KEY)
|
|
const b = Buffer.from(a)
|
|
b[b.length - 1] ^= 0x01
|
|
|
|
expect(fingerprintOf(b)).not.toBe(fingerprintOf(a))
|
|
})
|
|
|
|
it('is a SHA256 digest, not a truncated or padded one', () => {
|
|
// 32 bytes base64-encodes to 44 characters, 43 once padding is stripped.
|
|
const digest = fingerprintOf(blobOf(ED25519_PUBLIC_KEY)).replace('SHA256:', '')
|
|
expect(digest).toHaveLength(43)
|
|
})
|
|
})
|