62 lines
2.1 KiB
Swift
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
|
|
}
|