Größte offene Härtungslücke geschlossen: SSHTransport nutzte bisher .acceptAnything() für Host-Key-Validierung, akzeptierte also jeden Schlüssel ohne Prüfung -- ein Man-in-the-Middle im lokalen Netz wäre unbemerkt geblieben. Jetzt Trust-on-first-use wie beim REST-Zertifikat: - SSHHostKeyFingerprint: SHA256 über NIOSSHPublicKey.write(to:) (die SSH-Wire-Format-Bytes des Schlüssels) -- exakt die Bytes, die auch OpenSSH für seine SHA256:-Fingerabdrücke hasht. Per Unit-Test gegen einen echten ssh-keygen-erzeugten Testschlüssel kreuzgeprüft (SHA256:Hllxv6LLoHl2XTIXGGjUYJHbPFoH2F7iMrR74C5J95g), nicht geraten. - SSHHostKeyTrustStore: UserDefaults-Persistenz pro Host, Pendant zu CertificateTrustStore. - SSHTransport conformt jetzt selbst zu NIOSSHClientServerAuthentication- Delegate (wie RestTransport zu URLSessionDelegate) und übergibt sich selbst als .custom(self) Host-Key-Validator. - ConnectionService: neuer State .needsSSHHostKeyConfirmation, eigener Bestätigungs-Retry-Pfad (trustCurrentSSHHostKeyAndRetry), analog zum bestehenden Zertifikat-Flow. - ConnectView: zweiter Bestätigungsdialog mit Warnhinweis, dass ein geänderter Fingerabdruck bei zuvor schon verbundenen Routern auf ein manipuliertes Netzwerk hindeuten könnte. BackupService/FactoryResetService bekommen die TOFU-Prüfung automatisch mit (SSHTransport-Default-Parameter, gleicher UserDefaults-Speicher), ohne eigene Bestätigungs-UI -- in der Praxis unkritisch, da der Verbinden-Tab das Vertrauen immer zuerst herstellt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HReLXMbmPvtQ23p1iWiJNW
52 lines
1.5 KiB
Swift
52 lines
1.5 KiB
Swift
import Foundation
|
|
|
|
struct RouterOSCredentials: Equatable {
|
|
var host: String
|
|
var username: String
|
|
var password: String
|
|
var httpsPort: Int = 443
|
|
var sshPort: Int = 22
|
|
}
|
|
|
|
struct RouterDeviceInfo: Equatable {
|
|
var boardName: String
|
|
var routerOSVersion: String
|
|
var architecture: String
|
|
var uptime: String
|
|
}
|
|
|
|
struct NetworkInterface: Equatable, Identifiable {
|
|
var id: String { name }
|
|
var name: String
|
|
var type: String
|
|
var running: Bool
|
|
var disabled: Bool
|
|
var macAddress: String?
|
|
}
|
|
|
|
enum RouterOSError: LocalizedError, Equatable {
|
|
case notConnected
|
|
case invalidResponse(String)
|
|
case authenticationFailed
|
|
case untrustedCertificate(fingerprint: String)
|
|
case untrustedSSHHostKey(fingerprint: String)
|
|
case transportUnavailable(String)
|
|
|
|
var errorDescription: String? {
|
|
switch self {
|
|
case .notConnected:
|
|
return "Keine Verbindung zum Router."
|
|
case .invalidResponse(let detail):
|
|
return "Unerwartete Antwort vom Router: \(detail)"
|
|
case .authenticationFailed:
|
|
return "Anmeldung fehlgeschlagen. Bitte Zugangsdaten prüfen."
|
|
case .untrustedCertificate(let fingerprint):
|
|
return "Unbekanntes Zertifikat (Fingerabdruck \(fingerprint)). Bitte bestätigen."
|
|
case .untrustedSSHHostKey(let fingerprint):
|
|
return "Unbekannter SSH-Schlüssel (Fingerabdruck \(fingerprint)). Bitte bestätigen."
|
|
case .transportUnavailable(let detail):
|
|
return detail
|
|
}
|
|
}
|
|
}
|