Files
2026-03-07 18:19:16 +01:00

132 lines
4.2 KiB
Swift

import Foundation
import Security
public enum KeychainError: Error {
case duplicateEntry
case unknown(OSStatus)
case itemNotFound
case invalidItemFormat
}
public class KeychainBackupManager {
public static let shared = KeychainBackupManager()
private let service = "\(Bundle.main.bundleIdentifier!).sessionsbackup"
private init() {}
// MARK: - Save Credentials
public func saveSession(id: String, _ session: Data) throws {
// Create query dictionary
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: id,
kSecValueData as String: session,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked
]
// Add to keychain
let status = SecItemAdd(query as CFDictionary, nil)
if status == errSecDuplicateItem {
// Item already exists, update it
let updateQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: id
]
let attributesToUpdate: [String: Any] = [
kSecValueData as String: session
]
let updateStatus = SecItemUpdate(updateQuery as CFDictionary,
attributesToUpdate as CFDictionary)
if updateStatus != errSecSuccess {
throw KeychainError.unknown(updateStatus)
}
} else if status != errSecSuccess {
throw KeychainError.unknown(status)
}
}
// MARK: - Retrieve Credentials
public func retrieveSession(for id: String) throws -> Data {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: id,
kSecReturnData as String: true
]
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
guard status == errSecSuccess, let sessionData = result as? Data else {
throw KeychainError.itemNotFound
}
return sessionData
}
// MARK: - Delete Credentials
public func deleteSession(for id: String) throws {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: id
]
let status = SecItemDelete(query as CFDictionary)
if status != errSecSuccess && status != errSecItemNotFound {
throw KeychainError.unknown(status)
}
}
// MARK: - Retrieve All Accounts
public func getAllSessons() throws -> [Data] {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitAll
]
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
if status == errSecItemNotFound {
return []
}
guard status == errSecSuccess,
let credentialsDataArray = result as? [Data] else {
throw KeychainError.unknown(status)
}
return credentialsDataArray
}
// MARK: - Delete All Sessions
public func deleteAllSessions() throws {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service
]
let status = SecItemDelete(query as CFDictionary)
// If no items were found, that's fine - just return
if status == errSecItemNotFound {
return
}
// For any other error, throw
if status != errSecSuccess {
throw KeychainError.unknown(status)
}
}
}