import Foundation import Security /// Minimal Keychain wrapper for storing the MCMS credentials. Stores the /// password (and optionally host/email) keyed by a service + account. Never /// use UserDefaults for secrets. struct KeychainStore { let service: String init(service: String = "com.example.mcmsmobile.credentials") { self.service = service } @discardableResult func set(_ value: String, account: String) -> Bool { guard let data = value.data(using: .utf8) else { return false } 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 attributes[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly return SecItemAdd(attributes as CFDictionary, nil) == errSecSuccess } func get(account: String) -> String? { 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 item: CFTypeRef? guard SecItemCopyMatching(query as CFDictionary, &item) == errSecSuccess, let data = item as? Data else { return nil } return String(data: data, encoding: .utf8) } @discardableResult func delete(account: String) -> Bool { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, kSecAttrAccount as String: account ] return SecItemDelete(query as CFDictionary) == errSecSuccess } }