forked from kay/RouterOS
XcodeGen-basiertes SwiftUI-Projekt für den RouterOS-Interview-Assistenten. Erster Wizard-Schritt: Verbindung zu Mikrotik-Geräten per REST-API (RouterOS >=7.1) mit SSH-CLI-Fallback für ältere Firmware, Zertifikats- TOFU-Bestätigung, Zugangsdaten im Keychain. Unit-Tests für CLI-Parser und Fallback-Logik. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HReLXMbmPvtQ23p1iWiJNW
47 lines
1.7 KiB
Swift
47 lines
1.7 KiB
Swift
import Foundation
|
|
import Security
|
|
|
|
/// Stores router login passwords in the macOS Keychain, never in plaintext.
|
|
struct KeychainService {
|
|
private let service = "com.focus72.RouterOSAssistant"
|
|
|
|
func save(password: String, forHost host: String, username: String) {
|
|
let account = "\(username)@\(host)"
|
|
let query: [String: Any] = [
|
|
kSecClass as String: kSecClassGenericPassword,
|
|
kSecAttrService as String: service,
|
|
kSecAttrAccount as String: account
|
|
]
|
|
SecItemDelete(query as CFDictionary)
|
|
|
|
var attributes = query
|
|
attributes[kSecValueData as String] = Data(password.utf8)
|
|
SecItemAdd(attributes as CFDictionary, nil)
|
|
}
|
|
|
|
func loadPassword(forHost host: String, username: String) -> String? {
|
|
let account = "\(username)@\(host)"
|
|
let query: [String: Any] = [
|
|
kSecClass as String: kSecClassGenericPassword,
|
|
kSecAttrService as String: service,
|
|
kSecAttrAccount as String: account,
|
|
kSecReturnData as String: true,
|
|
kSecMatchLimit as String: kSecMatchLimitOne
|
|
]
|
|
var result: AnyObject?
|
|
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
guard status == errSecSuccess, let data = result as? Data else { return nil }
|
|
return String(data: data, encoding: .utf8)
|
|
}
|
|
|
|
func deletePassword(forHost host: String, username: String) {
|
|
let account = "\(username)@\(host)"
|
|
let query: [String: Any] = [
|
|
kSecClass as String: kSecClassGenericPassword,
|
|
kSecAttrService as String: service,
|
|
kSecAttrAccount as String: account
|
|
]
|
|
SecItemDelete(query as CFDictionary)
|
|
}
|
|
}
|