Offer to bring the keys an ssh_config points at
ci / build and test (push) Successful in 2m0s
ci / android head (push) Successful in 6m5s
ci / api image (push) Successful in 42s

An import that recorded a key path and left every host asking for a password was
an import whose result did not connect. The answer to that was a manual paste per
key, which is the sort of thing people do once and then stop importing.

So there is a tick, and it starts off. With it off nothing changes: an
IdentityFile becomes a note and the host asks for a password. With it on, IMPORT
reads each host's first IdentityFile out of ~/.ssh, stores it in the vault
encrypted like any other key, and binds the host to it.

Three things about how it is drawn are load-bearing rather than tidy. It is a
default nobody arrives at by accident. The sentence beside it names the directory
rather than saying "your keys", because that is what somebody is agreeing to. And
nothing is read during SCAN — tick it, read what it says, untick it, and no
private key has been opened. This is the only place the application opens key
material out of a directory the user did not point at file by file, and the whole
of what makes that acceptable is that it took a deliberate press.

One vault key per file, however many entries named it: an ssh_config pointing
twelve hosts at one id_ed25519 is the ordinary shape, and twelve copies would be
twelve things to rotate and eleven to forget. A file whose material is already in
the keychain is bound to rather than stored again, which is what makes running
the import twice harmless.

What cannot be read off a disk is a passphrase, so a protected key arrives
without one — and the report under the button names those files rather than
leaving a host to fail at connect time with a message about a malformed key.
Telling them apart means decoding for OpenSSH's own container, whose cipher name
is the first field inside the base64 rather than anything in the armour, and that
is the format ssh-keygen has written by default for years. The 88 base64
characters it decodes need 66 bytes, not 64: with the smaller span every
protected key came back unprotected, which the tests now pin.

A path that is not on this machine leaves its host imported and unbound, exactly
as it would have been with the tick off, and is named in the same report. A
config carried from another machine is the ordinary case, not an error.
This commit is contained in:
2026-08-05 08:58:28 +02:00
parent 746711da9d
commit ca07d63585
11 changed files with 1220 additions and 62 deletions
+19 -1
View File
@@ -69,7 +69,7 @@ src/
DodoSSH.Client.Terminal the loopback data plane and credit-based flow control
DodoSSH.Client.Transfer the transfer queue, part files and resume, and the local file listing
DodoSSH.Client.ObjectStore S3-compatible buckets, behind the same interface as SFTP
DodoSSH.Client.Import reading ~/.ssh/config, with no I/O of its own
DodoSSH.Client.Import reading ~/.ssh/config, and the keys it names when asked
DodoSSH.Client.Shell the view models both heads drive, the renderer's files, the palette
DodoSSH.Client.App the desktop head: its views, and its Windows integration
DodoSSH.Client.Android the phone head: its views, and its Android integration
@@ -231,6 +231,24 @@ Windows — is offered neither button and keeps asking for the passphrase, which
rather than leaving you to notice. The passphrase never stops working either way: a declined confirmation
leaves the box exactly where it was.
**Importing an `~/.ssh/config` can bring the keys with it, and only if you ask.** Preferences → IMPORT
HOSTS scans the file and shows what it found; nothing is stored until you press the button. Below the list
there is one tick — *Also import the private keys these hosts point at* — and it starts off.
With it off, an `IdentityFile` becomes a note recording the path, and the host asks for a password until you
bind it to a key yourself. With it on, IMPORT reads each host's first `IdentityFile` out of `~/.ssh`, stores
it in the vault encrypted like any other key, and binds the host to it. One key is stored per **file**
however many entries name it, and a file already in your keychain is bound to rather than stored twice, so
running the import again does not fill the keychain with copies.
**This is the only place DodoSSH opens a private key you did not point it at file by file**, which is why
the tick is off, why the sentence beside it names the directory, and why nothing is read during SCAN — you
can tick it, read what it says and untick it, and no key will have been opened. A key protected by a
passphrase comes in without one, because nothing on disk says what the passphrase is; the report under the
button names those files, and the keychain's editor is where the passphrase goes. A file that is not on this
machine — the ordinary case for a config carried from another one — leaves its host imported and unbound,
and is named in the same report.
### Moving files
**SFTP** and **S3** in the tab strip are a two-pane browser: this machine on the left, the remote on the