-
Notifications
You must be signed in to change notification settings - Fork 348
/
KeychainSwiftDistrib.swift
508 lines (346 loc) · 17.1 KB
/
KeychainSwiftDistrib.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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
//
// Keychain helper for iOS/Swift.
//
// https://github.com/evgenyneu/keychain-swift
//
// This file was automatically generated by combining multiple Swift source files.
//
// ----------------------------
//
// KeychainSwift.swift
//
// ----------------------------
import Security
import Foundation
/**
A collection of helper functions for saving text and data in the keychain.
*/
open class KeychainSwift {
var lastQueryParameters: [String: Any]? // Used by the unit tests
/// Contains result code from the last operation. Value is noErr (0) for a successful result.
open var lastResultCode: OSStatus = noErr
var keyPrefix = "" // Can be useful in test.
/**
Specify an access group that will be used to access keychain items. Access groups can be used to share keychain items between applications. When access group value is nil all application access groups are being accessed. Access group name is used by all functions: set, get, delete and clear.
*/
open var accessGroup: String?
/**
Specifies whether the items can be synchronized with other devices through iCloud. Setting this property to true will
add the item to other devices with the `set` method and obtain synchronizable items with the `get` command. Deleting synchronizable items will remove them from all devices. In order for keychain synchronization to work the user must enable "Keychain" in iCloud settings.
Does not work on macOS.
*/
open var synchronizable: Bool = false
private let lock = NSLock()
/// Instantiate a KeychainSwift object
public init() { }
/**
- parameter keyPrefix: a prefix that is added before the key in get/set methods. Note that `clear` method still clears everything from the Keychain.
*/
public init(keyPrefix: String) {
self.keyPrefix = keyPrefix
}
/**
Stores the text value in the keychain item under the given key.
- parameter key: Key under which the text value is stored in the keychain.
- parameter value: Text string to be written to the keychain.
- parameter withAccess: Value that indicates when your app needs access to the text in the keychain item. By default the .AccessibleWhenUnlocked option is used that permits the data to be accessed only while the device is unlocked by the user.
- returns: True if the text was successfully written to the keychain.
*/
@discardableResult
open func set(_ value: String, forKey key: String,
withAccess access: KeychainSwiftAccessOptions? = nil) -> Bool {
if let value = value.data(using: String.Encoding.utf8) {
return set(value, forKey: key, withAccess: access)
}
return false
}
/**
Stores the data in the keychain item under the given key.
- parameter key: Key under which the data is stored in the keychain.
- parameter value: Data to be written to the keychain.
- parameter withAccess: Value that indicates when your app needs access to the text in the keychain item. By default the .AccessibleWhenUnlocked option is used that permits the data to be accessed only while the device is unlocked by the user.
- returns: True if the text was successfully written to the keychain.
*/
@discardableResult
open func set(_ value: Data, forKey key: String,
withAccess access: KeychainSwiftAccessOptions? = nil) -> Bool {
// The lock prevents the code to be run simultaneously
// from multiple threads which may result in crashing
lock.lock()
defer { lock.unlock() }
deleteNoLock(key) // Delete any existing key before saving it
let accessible = access?.value ?? KeychainSwiftAccessOptions.defaultOption.value
let prefixedKey = keyWithPrefix(key)
var query: [String : Any] = [
KeychainSwiftConstants.klass : kSecClassGenericPassword,
KeychainSwiftConstants.attrAccount : prefixedKey,
KeychainSwiftConstants.valueData : value,
KeychainSwiftConstants.accessible : accessible
]
query = addAccessGroupWhenPresent(query)
query = addSynchronizableIfRequired(query, addingItems: true)
lastQueryParameters = query
lastResultCode = SecItemAdd(query as CFDictionary, nil)
return lastResultCode == noErr
}
/**
Stores the boolean value in the keychain item under the given key.
- parameter key: Key under which the value is stored in the keychain.
- parameter value: Boolean to be written to the keychain.
- parameter withAccess: Value that indicates when your app needs access to the value in the keychain item. By default the .AccessibleWhenUnlocked option is used that permits the data to be accessed only while the device is unlocked by the user.
- returns: True if the value was successfully written to the keychain.
*/
@discardableResult
open func set(_ value: Bool, forKey key: String,
withAccess access: KeychainSwiftAccessOptions? = nil) -> Bool {
let bytes: [UInt8] = value ? [1] : [0]
let data = Data(bytes)
return set(data, forKey: key, withAccess: access)
}
/**
Retrieves the text value from the keychain that corresponds to the given key.
- parameter key: The key that is used to read the keychain item.
- returns: The text value from the keychain. Returns nil if unable to read the item.
*/
open func get(_ key: String) -> String? {
if let data = getData(key) {
if let currentString = String(data: data, encoding: .utf8) {
return currentString
}
lastResultCode = -67853 // errSecInvalidEncoding
}
return nil
}
/**
Retrieves the data from the keychain that corresponds to the given key.
- parameter key: The key that is used to read the keychain item.
- parameter asReference: If true, returns the data as reference (needed for things like NEVPNProtocol).
- returns: The text value from the keychain. Returns nil if unable to read the item.
*/
open func getData(_ key: String, asReference: Bool = false) -> Data? {
// The lock prevents the code to be run simultaneously
// from multiple threads which may result in crashing
lock.lock()
defer { lock.unlock() }
let prefixedKey = keyWithPrefix(key)
var query: [String: Any] = [
KeychainSwiftConstants.klass : kSecClassGenericPassword,
KeychainSwiftConstants.attrAccount : prefixedKey,
KeychainSwiftConstants.matchLimit : kSecMatchLimitOne
]
if asReference {
query[KeychainSwiftConstants.returnReference] = kCFBooleanTrue
} else {
query[KeychainSwiftConstants.returnData] = kCFBooleanTrue
}
query = addAccessGroupWhenPresent(query)
query = addSynchronizableIfRequired(query, addingItems: false)
lastQueryParameters = query
var result: AnyObject?
lastResultCode = withUnsafeMutablePointer(to: &result) {
SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
}
if lastResultCode == noErr {
return result as? Data
}
return nil
}
/**
Retrieves the boolean value from the keychain that corresponds to the given key.
- parameter key: The key that is used to read the keychain item.
- returns: The boolean value from the keychain. Returns nil if unable to read the item.
*/
open func getBool(_ key: String) -> Bool? {
guard let data = getData(key) else { return nil }
guard let firstBit = data.first else { return nil }
return firstBit == 1
}
/**
Deletes the single keychain item specified by the key.
- parameter key: The key that is used to delete the keychain item.
- returns: True if the item was successfully deleted.
*/
@discardableResult
open func delete(_ key: String) -> Bool {
// The lock prevents the code to be run simultaneously
// from multiple threads which may result in crashing
lock.lock()
defer { lock.unlock() }
return deleteNoLock(key)
}
/**
Return all keys from keychain
- returns: An string array with all keys from the keychain.
*/
public var allKeys: [String] {
var query: [String: Any] = [
KeychainSwiftConstants.klass : kSecClassGenericPassword,
KeychainSwiftConstants.returnData : true,
KeychainSwiftConstants.returnAttributes: true,
KeychainSwiftConstants.returnReference: true,
KeychainSwiftConstants.matchLimit: KeychainSwiftConstants.secMatchLimitAll
]
query = addAccessGroupWhenPresent(query)
query = addSynchronizableIfRequired(query, addingItems: false)
var result: AnyObject?
let lastResultCode = withUnsafeMutablePointer(to: &result) {
SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
}
if lastResultCode == noErr {
return (result as? [[String: Any]])?.compactMap {
$0[KeychainSwiftConstants.attrAccount] as? String } ?? []
}
return []
}
/**
Same as `delete` but is only accessed internally, since it is not thread safe.
- parameter key: The key that is used to delete the keychain item.
- returns: True if the item was successfully deleted.
*/
@discardableResult
func deleteNoLock(_ key: String) -> Bool {
let prefixedKey = keyWithPrefix(key)
var query: [String: Any] = [
KeychainSwiftConstants.klass : kSecClassGenericPassword,
KeychainSwiftConstants.attrAccount : prefixedKey
]
query = addAccessGroupWhenPresent(query)
query = addSynchronizableIfRequired(query, addingItems: false)
lastQueryParameters = query
lastResultCode = SecItemDelete(query as CFDictionary)
return lastResultCode == noErr
}
/**
Deletes all Keychain items used by the app. Note that this method deletes all items regardless of the prefix settings used for initializing the class.
- returns: True if the keychain items were successfully deleted.
*/
@discardableResult
open func clear() -> Bool {
// The lock prevents the code to be run simultaneously
// from multiple threads which may result in crashing
lock.lock()
defer { lock.unlock() }
var query: [String: Any] = [ kSecClass as String : kSecClassGenericPassword ]
query = addAccessGroupWhenPresent(query)
query = addSynchronizableIfRequired(query, addingItems: false)
lastQueryParameters = query
lastResultCode = SecItemDelete(query as CFDictionary)
return lastResultCode == noErr
}
/// Returns the key with currently set prefix.
func keyWithPrefix(_ key: String) -> String {
return "\(keyPrefix)\(key)"
}
func addAccessGroupWhenPresent(_ items: [String: Any]) -> [String: Any] {
guard let accessGroup = accessGroup else { return items }
var result: [String: Any] = items
result[KeychainSwiftConstants.accessGroup] = accessGroup
return result
}
/**
Adds kSecAttrSynchronizable: kSecAttrSynchronizableAny` item to the dictionary when the `synchronizable` property is true.
- parameter items: The dictionary where the kSecAttrSynchronizable items will be added when requested.
- parameter addingItems: Use `true` when the dictionary will be used with `SecItemAdd` method (adding a keychain item). For getting and deleting items, use `false`.
- returns: the dictionary with kSecAttrSynchronizable item added if it was requested. Otherwise, it returns the original dictionary.
*/
func addSynchronizableIfRequired(_ items: [String: Any], addingItems: Bool) -> [String: Any] {
if !synchronizable { return items }
var result: [String: Any] = items
result[KeychainSwiftConstants.attrSynchronizable] = addingItems == true ? true : kSecAttrSynchronizableAny
return result
}
}
// ----------------------------
//
// TegKeychainConstants.swift
//
// ----------------------------
import Foundation
import Security
/// Constants used by the library
public struct KeychainSwiftConstants {
/// Specifies a Keychain access group. Used for sharing Keychain items between apps.
public static var accessGroup: String { return toString(kSecAttrAccessGroup) }
/**
A value that indicates when your app needs access to the data in a keychain item. The default value is AccessibleWhenUnlocked. For a list of possible values, see KeychainSwiftAccessOptions.
*/
public static var accessible: String { return toString(kSecAttrAccessible) }
/// Used for specifying a String key when setting/getting a Keychain value.
public static var attrAccount: String { return toString(kSecAttrAccount) }
/// Used for specifying synchronization of keychain items between devices.
public static var attrSynchronizable: String { return toString(kSecAttrSynchronizable) }
/// An item class key used to construct a Keychain search dictionary.
public static var klass: String { return toString(kSecClass) }
/// Specifies the number of values returned from the keychain. The library only supports single values.
public static var matchLimit: String { return toString(kSecMatchLimit) }
/// A return data type used to get the data from the Keychain.
public static var returnData: String { return toString(kSecReturnData) }
/// Used for specifying a value when setting a Keychain value.
public static var valueData: String { return toString(kSecValueData) }
/// Used for returning a reference to the data from the keychain
public static var returnReference: String { return toString(kSecReturnPersistentRef) }
/// A key whose value is a Boolean indicating whether or not to return item attributes
public static var returnAttributes : String { return toString(kSecReturnAttributes) }
/// A value that corresponds to matching an unlimited number of items
public static var secMatchLimitAll : String { return toString(kSecMatchLimitAll) }
static func toString(_ value: CFString) -> String {
return value as String
}
}
// ----------------------------
//
// KeychainSwiftAccessOptions.swift
//
// ----------------------------
import Security
/**
These options are used to determine when a keychain item should be readable. The default value is AccessibleWhenUnlocked.
*/
public enum KeychainSwiftAccessOptions {
/**
The data in the keychain item can be accessed only while the device is unlocked by the user.
This is recommended for items that need to be accessible only while the application is in the foreground. Items with this attribute migrate to a new device when using encrypted backups.
This is the default value for keychain items added without explicitly setting an accessibility constant.
*/
case accessibleWhenUnlocked
/**
The data in the keychain item can be accessed only while the device is unlocked by the user.
This is recommended for items that need to be accessible only while the application is in the foreground. Items with this attribute do not migrate to a new device. Thus, after restoring from a backup of a different device, these items will not be present.
*/
case accessibleWhenUnlockedThisDeviceOnly
/**
The data in the keychain item cannot be accessed after a restart until the device has been unlocked once by the user.
After the first unlock, the data remains accessible until the next restart. This is recommended for items that need to be accessed by background applications. Items with this attribute migrate to a new device when using encrypted backups.
*/
case accessibleAfterFirstUnlock
/**
The data in the keychain item cannot be accessed after a restart until the device has been unlocked once by the user.
After the first unlock, the data remains accessible until the next restart. This is recommended for items that need to be accessed by background applications. Items with this attribute do not migrate to a new device. Thus, after restoring from a backup of a different device, these items will not be present.
*/
case accessibleAfterFirstUnlockThisDeviceOnly
/**
The data in the keychain can only be accessed when the device is unlocked. Only available if a passcode is set on the device.
This is recommended for items that only need to be accessible while the application is in the foreground. Items with this attribute never migrate to a new device. After a backup is restored to a new device, these items are missing. No items can be stored in this class on devices without a passcode. Disabling the device passcode causes all items in this class to be deleted.
*/
case accessibleWhenPasscodeSetThisDeviceOnly
static var defaultOption: KeychainSwiftAccessOptions {
return .accessibleWhenUnlocked
}
var value: String {
switch self {
case .accessibleWhenUnlocked:
return toString(kSecAttrAccessibleWhenUnlocked)
case .accessibleWhenUnlockedThisDeviceOnly:
return toString(kSecAttrAccessibleWhenUnlockedThisDeviceOnly)
case .accessibleAfterFirstUnlock:
return toString(kSecAttrAccessibleAfterFirstUnlock)
case .accessibleAfterFirstUnlockThisDeviceOnly:
return toString(kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly)
case .accessibleWhenPasscodeSetThisDeviceOnly:
return toString(kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly)
}
}
func toString(_ value: CFString) -> String {
return KeychainSwiftConstants.toString(value)
}
}