import Foundation import Security /// Writes the VPN password into the login Keychain. /// /// Uses the Security framework directly rather than shelling out to /// `security add-generic-password -w `: command-line arguments are /// visible in `ps` to every process on the machine, so a password must never /// travel that way. /// /// This is the one place the app touches the secret at all, and only because /// the user typed it here on purpose. It goes straight into the Keychain and is /// never stored, logged or echoed anywhere else — reading it back is left to /// `security` at connect time. enum KeychainStore { static func exists(service: String, account: String) -> Bool { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, kSecAttrAccount as String: account, kSecReturnData as String: false, ] return SecItemCopyMatching(query as CFDictionary, nil) == errSecSuccess } /// Adds the item, or replaces the password if it is already there. static func save(service: String, account: String, password: String) -> String? { guard let data = password.data(using: .utf8) else { return "Не удалось закодировать пароль" } let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, kSecAttrAccount as String: account, ] let status: OSStatus if exists(service: service, account: account) { status = SecItemUpdate( query as CFDictionary, [kSecValueData as String: data] as CFDictionary) } else { var attributes = query attributes[kSecValueData as String] = data status = SecItemAdd(attributes as CFDictionary, nil) } guard status != errSecSuccess else { return nil } let message = SecCopyErrorMessageString(status, nil) as String? return message ?? "Ошибка Keychain \(status)" } static func delete(service: String, account: String) { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, kSecAttrAccount as String: account, ] SecItemDelete(query as CFDictionary) } }