Skip to content

Unlock passphrase-protected SSH keys - #111

Merged
timhartmann7 merged 12 commits into
mainfrom
fix/passphrase-protected-keys
Sep 26, 2026
Merged

timhartmann7 merged 12 commits into
mainfrom
fix/passphrase-protected-keys

Conversation

@timhartmann7

Copy link
Copy Markdown
Owner

A host whose identity file has a passphrase could not connect at all: both apps
failed with a bare "SSH authentication failed".

Builds on #103 by @sisodiahs — his commit fc94e3d is kept exactly as submitted.
Merge with "Create a merge commit" (not squash or rebase) so it lands on main
under his name and #103 is marked merged.

What was wrong

Keys were always read without a passphrase, so an encrypted identity_file (or
default key) failed with a generic authentication error and the terminal tab just
closed.

What changes

From #103: in-memory passphrase cache per canonical key path (ssh::identity), a
typed "passphrase required" outcome, KeyPassphraseRequired event, the prompt in
both apps, unlock_identity command.

On top of it:

  • wrong passphrase says so; a key changed on disk is asked for again; a key whose
    passphrase was removed loads as is
  • the typed error survives ProxyJump (bastion and target keys both prompt) and is
    never classified as a refusal; genuine refusals keep main's behaviour
  • connections that retry on their own (pollers, tunnels) ask once per key; a
    cancelled prompt comes back only when you open a session that needs the key
  • a poller wakes the moment its key is unlocked instead of waiting out backoff;
    a tunnel holds its ports and waits for the unlock instead of redialling
  • unlock_identity only accepts keys the core found encrypted
  • TUI: the prompt never takes keys on the terminal screen (status hint instead),
    sits above the update popup, paste types into it, Ctrl+C cancels it, the KDF runs
    off the render loop, fixed-height popup
  • GUI: prompts queue per key; the dialog takes focus explicitly (Svelte autofocus
    yields to a focused terminal, which would have received the passphrase); focus
    goes back when it closes; a terminal finishing its open never steals it; Escape
    closes only the top dialog
  • SFTP errors show the whole cause chain
  • dropped from fix(ssh): unlock passphrase-protected identity files #103: the Windows named-pipe agent (not needed for this fix — the
    prompt covers Windows too — and it can't be verified here; worth its own PR); FIDO id_*_sk default keys (russh can't sign
    with them)

Verification

  • fmt, clippy -D warnings (all targets), cargo test (core/TUI/GUI), svelte-check
    0/0, vitest 238/238, Playwright 38/39 (the one failure is pre-existing, below)
  • new tests: identity (7+3), session (2), tunnel integration (locked key waits,
    then redials on unlock), TUI render at 80x24, vitest queue/router, Playwright
    passphrase spec (6)
  • real Linux (Docker, OpenSSH): TUI with ed25519, RSA, two hosts sharing a key,
    encrypted default key, ProxyJump with both keys encrypted, wrong-then-right
    passphrase, cancel + no re-prompt after the backoff retry, terminal screen, paste,
    Ctrl+C; desktop app under Xvfb: prompt at launch, wrong passphrase, unlock,
    dashboard online, terminal and SFTP, cancel then terminal re-prompts

Waived review findings

  • tunnel on a locked key waits for the unlock with no timed redial — each redial
    would be a failed login; still better than main, which stopped it (Stop/Start
    resets)
  • passphrase cached (not the decrypted key), so each connect runs the KDF — keeps
    on-disk changes honest; pollers hold their sessions
  • passphrase not zeroized — same as the stored host passwords; needs a new dep
  • encrypted PKCS#8 keys still fail generically (not a regression; ssh-keygen writes
    OpenSSH format by default; russh can't decrypt every PKCS#8 variant)
  • only the first encrypted default key is reported — set identity_file to pick
  • a key held by the agent and encrypted on disk that the server refuses still
    prompts once (rare; the unlock then ends in a normal refusal)
  • a GUI webview reload drops a pending background prompt; opening a session asks
    again
  • a terminal/SFTP session that hit the locked key has to be reopened
  • contributor commit author and the merge commit break the commit rules on purpose,
    to credit @sisodiahs (as in fix(ssh): connect ProxyJump hosts through their bastion #75/Ship a native .rpm package for Fedora/RHEL #77)

Pre-existing (not touched)

  • e2e/terminal.spec.ts expects "Close web-1 · terminal"; the label is host-only
    since d898d96
  • eslint/knip are not configured in the repo

Supersedes #103.

@timhartmann7
timhartmann7 merged commit 4b30f16 into main Sep 26, 2026
6 checks passed
@timhartmann7
timhartmann7 deleted the fix/passphrase-protected-keys branch September 26, 2026 06:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants