forked from alexruperez/SecurePropertyStorage
-
Notifications
You must be signed in to change notification settings - Fork 0
/
KeychainStorage.swift
78 lines (68 loc) · 2.9 KB
/
KeychainStorage.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import CryptoKit
import Foundation
import Storage
open class KeychainStorage: DelegatedStorage {
open class var standard: KeychainStorage { shared }
private static let shared = KeychainStorage()
public convenience init(_ delegate: StorageDelegate = KeychainStorageDelegate(),
authenticationTag: Data? = nil,
errorClosure: StorageErrorClosure? = nil) {
self.init(delegate,
symmetricKey: SymmetricKey.generate(),
nonce: AES.GCM.Nonce.generate(),
authenticationTag: authenticationTag,
errorClosure: errorClosure)
}
}
open class KeychainStorageDelegate: StorageDelegate {
public init() {}
open func data<D: StorageData>(forKey key: StoreKey) throws -> D? {
try read(account: key)
}
open func set<D: StorageData>(_ data: D?, forKey key: StoreKey) throws {
try remove(forKey: key)
if let data = data {
try store(data, account: key)
}
}
open func remove(forKey key: StoreKey) throws {
try delete(account: key)
}
open func store<D: StorageData>(_ value: D,
account: String,
accessible: CFString = kSecAttrAccessibleWhenUnlocked) throws {
let query = [kSecClass: kSecClassGenericPassword,
kSecAttrAccount: account,
kSecAttrAccessible: accessible,
kSecUseDataProtectionKeychain: true,
kSecValueData: value.data] as [String: Any]
let status = SecItemAdd(query as CFDictionary, nil)
guard status == errSecSuccess else {
throw KeychainError.error("Unable to store item: \(status.message)")
}
}
open func read<D: StorageData>(account: String) throws -> D? {
let query = [kSecClass: kSecClassGenericPassword,
kSecAttrAccount: account,
kSecUseDataProtectionKeychain: true,
kSecReturnData: true] as [String: Any]
var item: CFTypeRef?
switch SecItemCopyMatching(query as CFDictionary, &item) {
case errSecSuccess:
guard let bytes = item as? Data else { return nil }
return try D(bytes: bytes)
case errSecItemNotFound: return nil
case let status: throw KeychainError.error("Keychain read failed: \(status.message)")
}
}
open func delete(account: String) throws {
let query = [kSecClass: kSecClassGenericPassword,
kSecUseDataProtectionKeychain: true,
kSecAttrAccount: account] as [String: Any]
switch SecItemDelete(query as CFDictionary) {
case errSecItemNotFound, errSecSuccess: break
case let status:
throw KeychainError.error("Unexpected deletion error: \(status.message)")
}
}
}