Files
multi-simadmin/apps/ios/Sources/MultiSimAdmin/Services/KeychainCredentialStore.swift
T

62 lines
2.1 KiB
Swift

import Foundation
import Security
public protocol CredentialStore: Sendable {
func get(instanceID: UUID) throws -> String?
func set(instanceID: UUID, password: String) throws
func remove(instanceID: UUID) throws
}
public struct KeychainCredentialStore: CredentialStore {
private let service = "fun.chick.multi-simadmin.instance-password"
public init() {}
public func get(instanceID: UUID) throws -> String? {
var query = baseQuery(instanceID)
query[kSecReturnData as String] = true
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard status == errSecSuccess else {
if status == errSecItemNotFound { return nil }
throw CredentialStoreError.keychainFailed
}
guard let data = item as? Data else { return nil }
return String(data: data, encoding: .utf8)
}
public func set(instanceID: UUID, password: String) throws {
try self.remove(instanceID: instanceID)
let attributes: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: instanceID.uuidString,
kSecValueData as String: Data(password.utf8),
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
]
let status = SecItemAdd(attributes as CFDictionary, nil)
guard status == errSecSuccess else {
throw CredentialStoreError.keychainFailed
}
}
public func remove(instanceID: UUID) throws {
let status = SecItemDelete(baseQuery(instanceID) as CFDictionary)
guard status == errSecSuccess || status == errSecItemNotFound else {
throw CredentialStoreError.keychainFailed
}
}
private func baseQuery(_ instanceID: UUID) -> [String: Any] {
[
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: instanceID.uuidString
]
}
}
public enum CredentialStoreError: Error, Equatable {
case keychainFailed
}