Public Access
Offer to bring the keys an ssh_config points at
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:
@@ -15,6 +15,17 @@
|
||||
Reachable from the preferences screen and not from the nav rail. It is a task rather than a
|
||||
destination — done once, or once a year — and a seventh rail entry would cost every screen a slot for
|
||||
something almost nobody is looking at.
|
||||
|
||||
── ◆ THE ONE TICK THAT READS PRIVATE KEYS ─────────────────────────────────────────────────────────────
|
||||
Below the list, off, and drawn only where the scan actually found an IdentityFile. It is the only control
|
||||
in this application that opens private key material out of a directory the user did not point at file by
|
||||
file, so 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 "keys", and nothing
|
||||
is read until IMPORT is pressed — so ticking it, reading the sentence and unticking it costs nothing.
|
||||
|
||||
What comes back afterwards is the report under the list: one line per key file, saying which were stored,
|
||||
which are protected by a passphrase this cannot know, and which were not there at all. That is reported
|
||||
rather than previewed for the same reason — previewing would mean reading them.
|
||||
-->
|
||||
|
||||
<Grid RowDefinitions="Auto,Auto,Auto,*,Auto">
|
||||
@@ -100,17 +111,57 @@
|
||||
IsVisible="{Binding HasRows}">
|
||||
<StackPanel Spacing="8">
|
||||
<!--
|
||||
Said before the button, not after. A key path is recorded and the key itself is not read: that is
|
||||
the difference between a bookmark that connects and one that asks for a password, and somebody
|
||||
who is not told will conclude the import was broken.
|
||||
Said before the button, not after. Whether the key material comes with the host is the difference
|
||||
between a bookmark that connects and one that asks for a password, and somebody who is not told
|
||||
will conclude the import was broken.
|
||||
-->
|
||||
<TextBlock Classes="hint" FontSize="11" TextWrapping="Wrap"
|
||||
Text="Key files are not read. Where ssh_config names an IdentityFile the path is recorded as a note, and the host asks for a password until you bind it to a key in your keychain. Nothing here reaches into ~/.ssh for private key material." />
|
||||
IsVisible="{Binding !ImportsKeys}"
|
||||
Text="Key files are not read. Where ssh_config names an IdentityFile the path is recorded as a note, and the host asks for a password until you bind it to a key in your keychain." />
|
||||
|
||||
<!--
|
||||
◆ THE TICK. Hidden entirely where the scan found no IdentityFile anywhere — an offer to read ~/.ssh
|
||||
on a screen where it would read nothing is a control that teaches people to ignore it.
|
||||
|
||||
The warning sentence appears only when it is on, and it is the one place this application says out
|
||||
loud that it is about to open private keys. It names the directory rather than saying "your keys",
|
||||
because what somebody is agreeing to is a read of that directory.
|
||||
-->
|
||||
<StackPanel Spacing="6" IsVisible="{Binding HasKeyFiles}">
|
||||
<CheckBox IsChecked="{Binding ImportsKeys}">
|
||||
<TextBlock Classes="mono" FontSize="11.5" TextWrapping="Wrap"
|
||||
Text="Also import the private keys these hosts point at" />
|
||||
</CheckBox>
|
||||
<TextBlock Classes="hint" FontSize="11" TextWrapping="Wrap"
|
||||
IsVisible="{Binding ImportsKeys}" Foreground="{StaticResource WarnText}"
|
||||
Text="Pressing IMPORT will read each host's first IdentityFile out of ~/.ssh, store it in this vault encrypted, and bind the host to it. One key is stored per file however many hosts name it, and a file already in your keychain is bound to rather than stored twice. A key protected by a passphrase comes in without one — nothing on disk says what it is — and the report below will name it." />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Button Classes="accent" Content="{Binding ImportLabel}" Command="{Binding ImportCommand}"
|
||||
IsEnabled="{Binding !IsBusy}" />
|
||||
<Button Classes="ghost" Content="TICK ALL / NONE" Command="{Binding ToggleAllCommand}" />
|
||||
</StackPanel>
|
||||
|
||||
<!--
|
||||
◆ What became of each key file, after the fact. Below the button because it is the answer rather
|
||||
than the offer, and capped with a scroll viewer because a config with thirty keyed hosts would
|
||||
otherwise push IMPORT off the window — the one control this screen must never lose.
|
||||
-->
|
||||
<Border IsVisible="{Binding HasKeyReport}" Padding="10,8" CornerRadius="4"
|
||||
Background="{StaticResource Panel}" BorderBrush="{StaticResource Border}"
|
||||
BorderThickness="1">
|
||||
<ScrollViewer MaxHeight="120">
|
||||
<ItemsControl ItemsSource="{Binding KeyReport}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="x:String">
|
||||
<TextBlock Text="{Binding}" Classes="hint" FontSize="10.5" TextWrapping="Wrap"
|
||||
Margin="0,2" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user