Public Access
Give DodoSSH a phone, and a shared shell for both heads to drive
The Android head from docs/android-port.md, taken as far as its step 6. Step 3, the spike, is answered and its throwaway screen is gone: libsodium.so and libe_sqlite3.so are both in the arm64 APK, so NSec resolves its native half on Android despite shipping no Android build, and the local cache opens. Two findings the audit could not have had: Avalonia.Controls.WebView only ships net10.0-android36.0, which settles the open "which Android versions" question at targetSdk 36; and Android has blocked cleartext HTTP since API 28, so the terminal renderer needs a network security config scoped to 127.0.0.1 or the WebView loads nothing. DodoSSH.Client.Shell is new and is why the phone can exist: the view models, the terminal renderer files and the palette moved there so both heads drive one state machine and draw from one set of tokens. The desktop head is otherwise untouched and its 144 tests still pass. The platform pieces behind interfaces that already existed: the profile directory from filesDir, a device key wrapped by a StrongBox-backed key that a fingerprint releases, and a foreground service so a shell outliving a vault lock stays true on a platform that stops backgrounded processes. Sign-in is deliberately absent rather than approximated. It needs an app link, because reusing the desktop loopback listener is the attack RFC 8252 section 8.3 names.
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:DodoSSH.Client.Shell.ViewModels"
|
||||
x:Class="DodoSSH.Client.Android.Views.LockedScreen"
|
||||
x:DataType="vm:MainWindowViewModel"
|
||||
Background="{StaticResource Canvas}">
|
||||
|
||||
<!--
|
||||
Design 01 — LOCKED, and one of the five states docs/android-port.md marks as most likely to be lost at
|
||||
360dp. Two things on it are load-bearing and neither is decoration:
|
||||
|
||||
the count of shells still connected, and the paragraph under it. Locking describes the keychain and not
|
||||
this phone's access to the hosts, and the desktop's README says so at length; on a phone, where the
|
||||
lock screen is most of what a user sees, saying it here is the only place it fits. The block is absent
|
||||
rather than empty when nothing is connected — a card reading "0 shells" would be noise on every launch.
|
||||
|
||||
Everything is one column with generous vertical slack above and below the controls, because the
|
||||
software keyboard takes roughly half the screen the moment the passphrase box is focused. The slack is
|
||||
what it eats.
|
||||
-->
|
||||
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<Grid RowDefinitions="*,Auto,Auto" Margin="24,0">
|
||||
|
||||
<!-- Identity. The chip names the account so a phone with two profiles is not a guess. -->
|
||||
<StackPanel Grid.Row="0" VerticalAlignment="Center" HorizontalAlignment="Center" Spacing="10" Margin="0,48,0,36">
|
||||
<Border Width="44" Height="44" BorderBrush="{StaticResource Accent}" BorderThickness="1"
|
||||
HorizontalAlignment="Center">
|
||||
<TextBlock Text=">_" Foreground="{StaticResource Accent}" FontFamily="{StaticResource MonoFont}"
|
||||
FontSize="16" FontWeight="SemiBold"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
|
||||
<TextBlock Text="DodoSSH" Foreground="{StaticResource Text}" FontFamily="{StaticResource MonoFont}"
|
||||
FontSize="15" FontWeight="SemiBold" HorizontalAlignment="Center" />
|
||||
|
||||
<Border BorderBrush="{StaticResource BorderMid}" BorderThickness="1" CornerRadius="3"
|
||||
Padding="9,3" HorizontalAlignment="Center"
|
||||
IsVisible="{Binding AccountName, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
<TextBlock Text="{Binding AccountName}" Foreground="{StaticResource TextDim}"
|
||||
FontFamily="{StaticResource MonoFont}" FontSize="10" FontWeight="Medium" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="1">
|
||||
|
||||
<!--
|
||||
Enter unlocks, which matters more here than on the desktop: the software keyboard's action key is
|
||||
the nearest thing to hand, and reaching past it to a button is the sort of friction that gets a
|
||||
phone client called slow.
|
||||
-->
|
||||
<TextBox Text="{Binding Passphrase}" PasswordChar="•" Watermark="vault passphrase"
|
||||
Height="48" Padding="14,0" VerticalContentAlignment="Center"
|
||||
Background="{StaticResource Field}" BorderBrush="{StaticResource BorderMid}"
|
||||
BorderThickness="1" CornerRadius="6" Foreground="{StaticResource Text}"
|
||||
FontFamily="{StaticResource MonoFont}" FontSize="12"
|
||||
IsEnabled="{Binding !IsBusy}">
|
||||
<TextBox.KeyBindings>
|
||||
<KeyBinding Gesture="Enter" Command="{Binding UnlockCommand}" />
|
||||
</TextBox.KeyBindings>
|
||||
</TextBox>
|
||||
|
||||
<Button Content="UNLOCK" Command="{Binding UnlockCommand}" IsEnabled="{Binding !IsBusy}"
|
||||
Height="48" Margin="0,10,0,0" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"
|
||||
Background="{StaticResource Accent}" Foreground="{StaticResource Canvas}"
|
||||
CornerRadius="6" FontFamily="{StaticResource MonoFont}" FontSize="12" FontWeight="SemiBold" />
|
||||
|
||||
<!--
|
||||
Present only when this phone actually holds a device key. The design draws it unconditionally,
|
||||
but offering a fingerprint that cannot open anything is worse than not offering one — see
|
||||
AndroidDeviceKeyStore for the three ordinary ways it stops being available.
|
||||
-->
|
||||
<Button Command="{Binding UnlockWithDeviceCommand}" IsVisible="{Binding CanUnlockWithDevice}"
|
||||
IsEnabled="{Binding !IsBusy}"
|
||||
Height="48" Margin="0,8,0,0" HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"
|
||||
Background="Transparent" BorderBrush="{StaticResource BorderMid}" BorderThickness="1"
|
||||
CornerRadius="6" Foreground="{StaticResource Text}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="9">
|
||||
<Ellipse Width="16" Height="16" Stroke="{StaticResource Accent}" StrokeThickness="1.5"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock Text="UNLOCK WITH FINGERPRINT" FontFamily="{StaticResource MonoFont}"
|
||||
FontSize="11" FontWeight="SemiBold" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<TextBlock Text="works with no network — the keychain decrypts on this phone"
|
||||
Foreground="{StaticResource TextFaint}" FontFamily="{StaticResource MonoFont}"
|
||||
FontSize="10" TextAlignment="Center" TextWrapping="Wrap" Margin="0,14,0,0" />
|
||||
|
||||
<TextBlock Text="{Binding StatusMessage}" Foreground="{StaticResource TextDim}"
|
||||
FontFamily="{StaticResource MonoFont}" FontSize="10" TextAlignment="Center"
|
||||
TextWrapping="Wrap" Margin="0,8,0,0" />
|
||||
|
||||
<!--
|
||||
◆ The disclosure. Absent when there is nothing to disclose; never a card reading zero.
|
||||
-->
|
||||
<Border IsVisible="{Binding HasLiveSessions}" Margin="0,22,0,0"
|
||||
Background="{StaticResource WarnWash}" BorderBrush="{StaticResource WarnSoft}"
|
||||
BorderThickness="1" CornerRadius="6" Padding="14,12">
|
||||
<StackPanel Spacing="6">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Ellipse Width="6" Height="6" Fill="{StaticResource Accent}" VerticalAlignment="Center" />
|
||||
<TextBlock Text="{Binding LiveSessionSummary}" Foreground="{StaticResource Warn}"
|
||||
FontFamily="{StaticResource MonoFont}" FontSize="10" FontWeight="SemiBold"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<TextBlock Foreground="{StaticResource WarnText}" FontFamily="{StaticResource MonoFont}"
|
||||
FontSize="10" TextWrapping="Wrap"
|
||||
Text="Locked describes the keychain — not this phone's access to the hosts. Open sessions stay alive behind this screen." />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<!--
|
||||
The only answer to a forgotten passphrase, and it is deliberately the last thing on the screen and
|
||||
the only red one. Nothing can recover a passphrase; this empties the phone and starts again.
|
||||
-->
|
||||
<Button Grid.Row="2" Command="{Binding SignOutCommand}"
|
||||
Margin="0,28,0,20" Padding="0,14" HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Center" Background="Transparent" BorderThickness="0"
|
||||
Foreground="{StaticResource Danger}" FontFamily="{StaticResource MonoFont}"
|
||||
FontSize="10.5" FontWeight="Medium"
|
||||
Content="RESET THIS PHONE — forgot passphrase" />
|
||||
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
|
||||
</UserControl>
|
||||
Reference in New Issue
Block a user