added iOS source code
[wl-app.git] / iOS / Pods / SwiftKeychainWrapper / SwiftKeychainWrapper / KeychainWrapper.swift
1 //
2 //  KeychainWrapper.swift
3 //  KeychainWrapper
4 //
5 //  Created by Jason Rendel on 9/23/14.
6 //  Copyright (c) 2014 Jason Rendel. All rights reserved.
7 //
8 //    The MIT License (MIT)
9 //
10 //    Permission is hereby granted, free of charge, to any person obtaining a copy
11 //    of this software and associated documentation files (the "Software"), to deal
12 //    in the Software without restriction, including without limitation the rights
13 //    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 //    copies of the Software, and to permit persons to whom the Software is
15 //    furnished to do so, subject to the following conditions:
16 //
17 //    The above copyright notice and this permission notice shall be included in all
18 //    copies or substantial portions of the Software.
19 //
20 //    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 //    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 //    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 //    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 //    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 //    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26 //    SOFTWARE.
27
28 import Foundation
29
30
31 private let SecMatchLimit: String! = kSecMatchLimit as String
32 private let SecReturnData: String! = kSecReturnData as String
33 private let SecReturnPersistentRef: String! = kSecReturnPersistentRef as String
34 private let SecValueData: String! = kSecValueData as String
35 private let SecAttrAccessible: String! = kSecAttrAccessible as String
36 private let SecClass: String! = kSecClass as String
37 private let SecAttrService: String! = kSecAttrService as String
38 private let SecAttrGeneric: String! = kSecAttrGeneric as String
39 private let SecAttrAccount: String! = kSecAttrAccount as String
40 private let SecAttrAccessGroup: String! = kSecAttrAccessGroup as String
41 private let SecReturnAttributes: String = kSecReturnAttributes as String
42
43 /// KeychainWrapper is a class to help make Keychain access in Swift more straightforward. It is designed to make accessing the Keychain services more like using NSUserDefaults, which is much more familiar to people.
44 open class KeychainWrapper {
45     
46     @available(*, deprecated: 2.2.1, message: "KeychainWrapper.defaultKeychainWrapper is deprecated, use KeychainWrapper.standard instead")
47     public static let defaultKeychainWrapper = KeychainWrapper.standard
48     
49     /// Default keychain wrapper access
50     public static let standard = KeychainWrapper()
51     
52     /// ServiceName is used for the kSecAttrService property to uniquely identify this keychain accessor. If no service name is specified, KeychainWrapper will default to using the bundleIdentifier.
53     private (set) public var serviceName: String
54     
55     /// AccessGroup is used for the kSecAttrAccessGroup property to identify which Keychain Access Group this entry belongs to. This allows you to use the KeychainWrapper with shared keychain access between different applications.
56     private (set) public var accessGroup: String?
57     
58     private static let defaultServiceName: String = {
59         return Bundle.main.bundleIdentifier ?? "SwiftKeychainWrapper"
60     }()
61
62     private convenience init() {
63         self.init(serviceName: KeychainWrapper.defaultServiceName)
64     }
65     
66     /// Create a custom instance of KeychainWrapper with a custom Service Name and optional custom access group.
67     ///
68     /// - parameter serviceName: The ServiceName for this instance. Used to uniquely identify all keys stored using this keychain wrapper instance.
69     /// - parameter accessGroup: Optional unique AccessGroup for this instance. Use a matching AccessGroup between applications to allow shared keychain access.
70     public init(serviceName: String, accessGroup: String? = nil) {
71         self.serviceName = serviceName
72         self.accessGroup = accessGroup
73     }
74
75     // MARK:- Public Methods
76     
77     /// Checks if keychain data exists for a specified key.
78     ///
79     /// - parameter forKey: The key to check for.
80     /// - parameter withAccessibility: Optional accessibility to use when retrieving the keychain item.
81     /// - returns: True if a value exists for the key. False otherwise.
82     open func hasValue(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool {
83         if let _ = data(forKey: key, withAccessibility: accessibility) {
84             return true
85         } else {
86             return false
87         }
88     }
89     
90     open func accessibilityOfKey(_ key: String) -> KeychainItemAccessibility? {
91         var keychainQueryDictionary = setupKeychainQueryDictionary(forKey: key)
92         var result: AnyObject?
93
94         // Remove accessibility attribute
95         keychainQueryDictionary.removeValue(forKey: SecAttrAccessible)
96         
97         // Limit search results to one
98         keychainQueryDictionary[SecMatchLimit] = kSecMatchLimitOne
99
100         // Specify we want SecAttrAccessible returned
101         keychainQueryDictionary[SecReturnAttributes] = kCFBooleanTrue
102
103             // Search
104         let status = withUnsafeMutablePointer(to: &result) {
105             SecItemCopyMatching(keychainQueryDictionary as CFDictionary, UnsafeMutablePointer($0))
106         }
107
108         if status == noErr {
109             if let resultsDictionary = result as? [String:AnyObject], let accessibilityAttrValue = resultsDictionary[SecAttrAccessible] as? String {
110                 return KeychainItemAccessibility.accessibilityForAttributeValue(accessibilityAttrValue as CFString)
111             }
112         }
113         
114         return nil
115     }
116     
117     // MARK: Public Getters
118     
119     open func integer(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Int? {
120         guard let numberValue = object(forKey: key, withAccessibility: accessibility) as? NSNumber else {
121             return nil
122         }
123         
124         return numberValue.intValue
125     }
126     
127     open func float(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Float? {
128         guard let numberValue = object(forKey: key, withAccessibility: accessibility) as? NSNumber else {
129             return nil
130         }
131         
132         return numberValue.floatValue
133     }
134     
135     open func double(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Double? {
136         guard let numberValue = object(forKey: key, withAccessibility: accessibility) as? NSNumber else {
137             return nil
138         }
139         
140         return numberValue.doubleValue
141     }
142     
143     open func bool(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool? {
144         guard let numberValue = object(forKey: key, withAccessibility: accessibility) as? NSNumber else {
145             return nil
146         }
147         
148         return numberValue.boolValue
149     }
150     
151     /// Returns a string value for a specified key.
152     ///
153     /// - parameter forKey: The key to lookup data for.
154     /// - parameter withAccessibility: Optional accessibility to use when retrieving the keychain item.
155     /// - returns: The String associated with the key if it exists. If no data exists, or the data found cannot be encoded as a string, returns nil.
156     open func string(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> String? {
157         guard let keychainData = data(forKey: key, withAccessibility: accessibility) else {
158             return nil
159         }
160         
161         return String(data: keychainData, encoding: String.Encoding.utf8) as String?
162     }
163     
164     /// Returns an object that conforms to NSCoding for a specified key.
165     ///
166     /// - parameter forKey: The key to lookup data for.
167     /// - parameter withAccessibility: Optional accessibility to use when retrieving the keychain item.
168     /// - returns: The decoded object associated with the key if it exists. If no data exists, or the data found cannot be decoded, returns nil.
169     open func object(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> NSCoding? {
170         guard let keychainData = data(forKey: key, withAccessibility: accessibility) else {
171             return nil
172         }
173         
174         return NSKeyedUnarchiver.unarchiveObject(with: keychainData) as? NSCoding
175     }
176
177     
178     /// Returns a Data object for a specified key.
179     ///
180     /// - parameter forKey: The key to lookup data for.
181     /// - parameter withAccessibility: Optional accessibility to use when retrieving the keychain item.
182     /// - returns: The Data object associated with the key if it exists. If no data exists, returns nil.
183     open func data(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Data? {
184         var keychainQueryDictionary = setupKeychainQueryDictionary(forKey: key, withAccessibility: accessibility)
185         var result: AnyObject?
186         
187         // Limit search results to one
188         keychainQueryDictionary[SecMatchLimit] = kSecMatchLimitOne
189         
190         // Specify we want Data/CFData returned
191         keychainQueryDictionary[SecReturnData] = kCFBooleanTrue
192         
193         // Search
194         let status = withUnsafeMutablePointer(to: &result) {
195             SecItemCopyMatching(keychainQueryDictionary as CFDictionary, UnsafeMutablePointer($0))
196         }
197         
198         return status == noErr ? result as? Data : nil
199     }
200     
201     
202     /// Returns a persistent data reference object for a specified key.
203     ///
204     /// - parameter forKey: The key to lookup data for.
205     /// - parameter withAccessibility: Optional accessibility to use when retrieving the keychain item.
206     /// - returns: The persistent data reference object associated with the key if it exists. If no data exists, returns nil.
207     open func dataRef(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Data? {
208         var keychainQueryDictionary = setupKeychainQueryDictionary(forKey: key, withAccessibility: accessibility)
209         var result: AnyObject?
210         
211         // Limit search results to one
212         keychainQueryDictionary[SecMatchLimit] = kSecMatchLimitOne
213         
214         // Specify we want persistent Data/CFData reference returned
215         keychainQueryDictionary[SecReturnPersistentRef] = kCFBooleanTrue
216         
217         // Search
218         let status = withUnsafeMutablePointer(to: &result) {
219             SecItemCopyMatching(keychainQueryDictionary as CFDictionary, UnsafeMutablePointer($0))
220         }
221         
222         return status == noErr ? result as? Data : nil
223     }
224     
225     // MARK: Public Setters
226     
227     @discardableResult open func set(_ value: Int, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool {
228         return set(NSNumber(value: value), forKey: key, withAccessibility: accessibility)
229     }
230     
231     @discardableResult open func set(_ value: Float, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool {
232         return set(NSNumber(value: value), forKey: key, withAccessibility: accessibility)
233     }
234     
235     @discardableResult open func set(_ value: Double, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool {
236         return set(NSNumber(value: value), forKey: key, withAccessibility: accessibility)
237     }
238     
239     @discardableResult open func set(_ value: Bool, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool {
240         return set(NSNumber(value: value), forKey: key, withAccessibility: accessibility)
241     }
242
243     /// Save a String value to the keychain associated with a specified key. If a String value already exists for the given key, the string will be overwritten with the new value.
244     ///
245     /// - parameter value: The String value to save.
246     /// - parameter forKey: The key to save the String under.
247     /// - parameter withAccessibility: Optional accessibility to use when setting the keychain item.
248     /// - returns: True if the save was successful, false otherwise.
249     @discardableResult open func set(_ value: String, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool {
250         if let data = value.data(using: .utf8) {
251             return set(data, forKey: key, withAccessibility: accessibility)
252         } else {
253             return false
254         }
255     }
256     
257     
258
259     /// Save an NSCoding compliant object to the keychain associated with a specified key. If an object already exists for the given key, the object will be overwritten with the new value.
260     ///
261     /// - parameter value: The NSCoding compliant object to save.
262     /// - parameter forKey: The key to save the object under.
263     /// - parameter withAccessibility: Optional accessibility to use when setting the keychain item.
264     /// - returns: True if the save was successful, false otherwise.
265     @discardableResult open func set(_ value: NSCoding, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool {
266         let data = NSKeyedArchiver.archivedData(withRootObject: value)
267         
268         return set(data, forKey: key, withAccessibility: accessibility)
269     }
270
271     /// Save a Data object to the keychain associated with a specified key. If data already exists for the given key, the data will be overwritten with the new value.
272     ///
273     /// - parameter value: The Data object to save.
274     /// - parameter forKey: The key to save the object under.
275     /// - parameter withAccessibility: Optional accessibility to use when setting the keychain item.
276     /// - returns: True if the save was successful, false otherwise.
277     @discardableResult open func set(_ value: Data, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool {
278         var keychainQueryDictionary: [String:Any] = setupKeychainQueryDictionary(forKey: key, withAccessibility: accessibility)
279         
280         keychainQueryDictionary[SecValueData] = value
281         
282         if let accessibility = accessibility {
283             keychainQueryDictionary[SecAttrAccessible] = accessibility.keychainAttrValue
284         } else {
285             // Assign default protection - Protect the keychain entry so it's only valid when the device is unlocked
286             keychainQueryDictionary[SecAttrAccessible] = KeychainItemAccessibility.whenUnlocked.keychainAttrValue
287         }
288         
289         let status: OSStatus = SecItemAdd(keychainQueryDictionary as CFDictionary, nil)
290         
291         if status == errSecSuccess {
292             return true
293         } else if status == errSecDuplicateItem {
294             return update(value, forKey: key, withAccessibility: accessibility)
295         } else {
296             return false
297         }
298     }
299
300     @available(*, deprecated: 2.2.1, message: "remove is deprecated, use removeObject instead")
301     @discardableResult open func remove(key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool {
302         return removeObject(forKey: key, withAccessibility: accessibility)
303     }
304     
305     /// Remove an object associated with a specified key. If re-using a key but with a different accessibility, first remove the previous key value using removeObjectForKey(:withAccessibility) using the same accessibilty it was saved with.
306     ///
307     /// - parameter forKey: The key value to remove data for.
308     /// - parameter withAccessibility: Optional accessibility level to use when looking up the keychain item.
309     /// - returns: True if successful, false otherwise.
310     @discardableResult open func removeObject(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool {
311         let keychainQueryDictionary: [String:Any] = setupKeychainQueryDictionary(forKey: key, withAccessibility: accessibility)
312
313         // Delete
314         let status: OSStatus = SecItemDelete(keychainQueryDictionary as CFDictionary)
315
316         if status == errSecSuccess {
317             return true
318         } else {
319             return false
320         }
321     }
322
323     /// Remove all keychain data added through KeychainWrapper. This will only delete items matching the currnt ServiceName and AccessGroup if one is set.
324     open func removeAllKeys() -> Bool {
325         // Setup dictionary to access keychain and specify we are using a generic password (rather than a certificate, internet password, etc)
326         var keychainQueryDictionary: [String:Any] = [SecClass:kSecClassGenericPassword]
327         
328         // Uniquely identify this keychain accessor
329         keychainQueryDictionary[SecAttrService] = serviceName
330         
331         // Set the keychain access group if defined
332         if let accessGroup = self.accessGroup {
333             keychainQueryDictionary[SecAttrAccessGroup] = accessGroup
334         }
335         
336         let status: OSStatus = SecItemDelete(keychainQueryDictionary as CFDictionary)
337         
338         if status == errSecSuccess {
339             return true
340         } else {
341             return false
342         }
343     }
344     
345     /// Remove all keychain data, including data not added through keychain wrapper.
346     ///
347     /// - Warning: This may remove custom keychain entries you did not add via SwiftKeychainWrapper.
348     ///
349     open class func wipeKeychain() {
350         deleteKeychainSecClass(kSecClassGenericPassword) // Generic password items
351         deleteKeychainSecClass(kSecClassInternetPassword) // Internet password items
352         deleteKeychainSecClass(kSecClassCertificate) // Certificate items
353         deleteKeychainSecClass(kSecClassKey) // Cryptographic key items
354         deleteKeychainSecClass(kSecClassIdentity) // Identity items
355     }
356
357     // MARK:- Private Methods
358     
359     /// Remove all items for a given Keychain Item Class
360     ///
361     ///
362     @discardableResult private class func deleteKeychainSecClass(_ secClass: AnyObject) -> Bool {
363         let query = [SecClass: secClass]
364         let status: OSStatus = SecItemDelete(query as CFDictionary)
365         
366         if status == errSecSuccess {
367             return true
368         } else {
369             return false
370         }
371     }
372     
373     /// Update existing data associated with a specified key name. The existing data will be overwritten by the new data
374     private func update(_ value: Data, forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> Bool {
375         var keychainQueryDictionary: [String:Any] = setupKeychainQueryDictionary(forKey: key, withAccessibility: accessibility)
376         let updateDictionary = [SecValueData:value]
377         
378         // on update, only set accessibility if passed in
379         if let accessibility = accessibility {
380             keychainQueryDictionary[SecAttrAccessible] = accessibility.keychainAttrValue
381         }
382         
383         // Update
384         let status: OSStatus = SecItemUpdate(keychainQueryDictionary as CFDictionary, updateDictionary as CFDictionary)
385
386         if status == errSecSuccess {
387             return true
388         } else {
389             return false
390         }
391     }
392
393     /// Setup the keychain query dictionary used to access the keychain on iOS for a specified key name. Takes into account the Service Name and Access Group if one is set.
394     ///
395     /// - parameter forKey: The key this query is for
396     /// - parameter withAccessibility: Optional accessibility to use when setting the keychain item. If none is provided, will default to .WhenUnlocked
397     /// - returns: A dictionary with all the needed properties setup to access the keychain on iOS
398     private func setupKeychainQueryDictionary(forKey key: String, withAccessibility accessibility: KeychainItemAccessibility? = nil) -> [String:Any] {
399         // Setup default access as generic password (rather than a certificate, internet password, etc)
400         var keychainQueryDictionary: [String:Any] = [SecClass:kSecClassGenericPassword]
401         
402         // Uniquely identify this keychain accessor
403         keychainQueryDictionary[SecAttrService] = serviceName
404         
405         // Only set accessibiilty if its passed in, we don't want to default it here in case the user didn't want it set
406         if let accessibility = accessibility {
407             keychainQueryDictionary[SecAttrAccessible] = accessibility.keychainAttrValue
408         }
409         
410         // Set the keychain access group if defined
411         if let accessGroup = self.accessGroup {
412             keychainQueryDictionary[SecAttrAccessGroup] = accessGroup
413         }
414         
415         // Uniquely identify the account who will be accessing the keychain
416         let encodedIdentifier: Data? = key.data(using: String.Encoding.utf8)
417         
418         keychainQueryDictionary[SecAttrGeneric] = encodedIdentifier
419         
420         keychainQueryDictionary[SecAttrAccount] = encodedIdentifier
421         
422         return keychainQueryDictionary
423     }
424 }