SSH Connection
Understanding SSH connections in Flutter Server Box.
Connection Flow
Section titled “Connection Flow”User Input → Spi Config → genClient() → SSH Client → SessionStep 1: Configuration
Section titled “Step 1: Configuration”The Spi (Server Parameter Info) model contains:
class Spi { String name; // Server name String ip; // IP address int port; // SSH port (default 22) String user; // Username String? pwd; // Password (encrypted) String? keyId; // SSH key ID String? jumpId; // Jump server ID String? alterUrl; // Alternative URL}Step 2: Client Generation
Section titled “Step 2: Client Generation”genClient(spi) creates SSH client:
Future<SSHClient> genClient(Spi spi) async { // 1. Establish socket final socket = await connect(spi.ip, spi.port);
// 2. Try alternative URL if failed if (socket == null && spi.alterUrl != null) { socket = await connect(spi.alterUrl, spi.port); }
// 3. Authenticate final client = SSHClient( socket: socket, username: spi.user, onPasswordRequest: () => spi.pwd, onIdentityRequest: () => loadKey(spi.keyId), );
// 4. Verify host key await verifyHostKey(client, spi);
return client;}Step 3: Jump Server (if configured)
Section titled “Step 3: Jump Server (if configured)”For jump servers, recursive connection:
if (spi.jumpId != null) { final jumpClient = await genClient(getJumpSpi(spi.jumpId)); final forwarded = await jumpClient.forwardLocal( spi.ip, spi.port, ); // Connect through forwarded socket}Authentication Methods
Section titled “Authentication Methods”Password Authentication
Section titled “Password Authentication”onPasswordRequest: () => spi.pwd- Password stored encrypted in Hive
- Decrypted on connection
- Sent to server for verification
Private Key Authentication
Section titled “Private Key Authentication”onIdentityRequest: () async { final key = await KeyStore.get(spi.keyId); return decyptPem(key.pem, key.password);}Key Loading Process:
- Retrieve encrypted key from
KeyStore - Decrypt password (biometric/prompt)
- Parse PEM format
- Standardize line endings (LF)
- Return for authentication
Keyboard-Interactive
Section titled “Keyboard-Interactive”onUserInfoRequest: (instructions) async { // Handle challenge-response return responses;}Supports:
- Password authentication
- OTP tokens
- Two-factor authentication
Host Key Verification
Section titled “Host Key Verification”Why Verify Host Keys?
Section titled “Why Verify Host Keys?”Prevents Man-in-the-Middle (MITM) attacks by ensuring you’re connecting to the same server.
Storage Format
Section titled “Storage Format”{spi.id}::{keyType}Example:
my-server::ssh-ed25519my-server::ecdsa-sha2-nistp256Fingerprint Formats
Section titled “Fingerprint Formats”MD5 Hex:
aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99Base64:
SHA256:AbCdEf1234567890...=Verification Flow
Section titled “Verification Flow”Future<void> verifyHostKey(SSHClient client, Spi spi) async { final key = await client.hostKey; final fingerprint = md5Hex(key); // or base64
final stored = SettingStore.sshKnownHostsFingerprints ['$keyId::$keyType'];
if (stored == null) { // New host - prompt user final trust = await promptUser( 'Unknown host', 'Fingerprint: $fingerprint', ); if (trust) { SettingStore.sshKnownHostsFingerprints ['$keyId::$keyType'] = fingerprint; } } else if (stored != fingerprint) { // Changed - warn user await warnUser( 'Host key changed!', 'Possible MITM attack', ); }}Session Management
Section titled “Session Management”Connection Pooling
Section titled “Connection Pooling”Active clients maintained in ServerProvider:
class ServerProvider { final Map<String, SSHClient> _clients = {};
SSHClient getClient(String spiId) { return _clients[spiId] ??= connect(spiId); }}Keep-Alive
Section titled “Keep-Alive”Maintain connection during inactivity:
Timer.periodic( Duration(seconds: 30), (_) => client.sendKeepAlive(),);Auto-Reconnect
Section titled “Auto-Reconnect”On connection loss:
client.onError.listen((error) async { await Future.delayed(Duration(seconds: 5)); reconnect();});Connection Lifecycle
Section titled “Connection Lifecycle”┌─────────────┐│ Initial │└──────┬──────┘ │ connect() ↓┌─────────────┐│ Connecting │ ←──┐└──────┬──────┘ │ │ success │ ↓ │ fail (retry)┌─────────────┐ ││ Connected │───┘└──────┬──────┘ │ ↓┌─────────────┐│ Active │ ──→ Send commands└──────┬──────┘ │ ↓ (error/disconnect)┌─────────────┐│ Disconnected│└─────────────┘Error Handling
Section titled “Error Handling”Connection Timeout
Section titled “Connection Timeout”try { await client.connect().timeout( Duration(seconds: 30), );} on TimeoutException { throw ConnectionException('Connection timeout');}Authentication Failure
Section titled “Authentication Failure”onAuthFail: (error) { if (error.contains('password')) { return 'Invalid password'; } else if (error.contains('key')) { return 'Invalid SSH key'; } return 'Authentication failed';}Host Key Mismatch
Section titled “Host Key Mismatch”onHostKeyMismatch: (stored, current) { showSecurityWarning( 'Host key has changed!', 'Possible MITM attack', );}Performance Considerations
Section titled “Performance Considerations”Connection Reuse
Section titled “Connection Reuse”- Reuse clients across features
- Don’t disconnect/reconnect unnecessarily
- Pool connections for concurrent operations
Optimal Settings
Section titled “Optimal Settings”- Timeout: 30 seconds (adjustable)
- Keep-alive: Every 30 seconds
- Retry delay: 5 seconds
Network Efficiency
Section titled “Network Efficiency”- Single connection for multiple operations
- Pipeline commands when possible
- Avoid opening multiple connections