import Foundation import Security enum KeychainCredentialStore { private static let service = "local.firelink.site-login" static func password(for id: UUID) -> String? { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, kSecAttrAccount as String: id.uuidString, kSecReturnData as String: true, kSecMatchLimit as String: kSecMatchLimitOne, kSecUseAuthenticationUI as String: kSecUseAuthenticationUIFail ] var result: CFTypeRef? guard SecItemCopyMatching(query as CFDictionary, &result) == errSecSuccess, let data = result as? Data else { return nil } return String(data: data, encoding: .utf8) } @discardableResult static func setPassword(_ password: String, for id: UUID) -> Bool { deletePassword(for: id) let attributes: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, kSecAttrAccount as String: id.uuidString, kSecValueData as String: Data(password.utf8), kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly ] return SecItemAdd(attributes as CFDictionary, nil) == errSecSuccess } @discardableResult static func deletePassword(for id: UUID) -> Bool { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: service, kSecAttrAccount as String: id.uuidString ] let status = SecItemDelete(query as CFDictionary) return status == errSecSuccess || status == errSecItemNotFound } private static let extensionTokenService = "local.firelink.extension-token" private static let extensionTokenAccount = "pairing-token" static func extensionToken() -> String? { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: extensionTokenService, kSecAttrAccount as String: extensionTokenAccount, kSecReturnData as String: true, kSecMatchLimit as String: kSecMatchLimitOne, kSecUseAuthenticationUI as String: kSecUseAuthenticationUIFail ] var result: CFTypeRef? guard SecItemCopyMatching(query as CFDictionary, &result) == errSecSuccess, let data = result as? Data else { return nil } return String(data: data, encoding: .utf8) } @discardableResult static func setExtensionToken(_ token: String) -> Bool { deleteExtensionToken() let attributes: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: extensionTokenService, kSecAttrAccount as String: extensionTokenAccount, kSecValueData as String: Data(token.utf8), kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly ] return SecItemAdd(attributes as CFDictionary, nil) == errSecSuccess } @discardableResult static func deleteExtensionToken() -> Bool { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: extensionTokenService, kSecAttrAccount as String: extensionTokenAccount ] let status = SecItemDelete(query as CFDictionary) return status == errSecSuccess || status == errSecItemNotFound } }