-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathKeychainWrapper.swift
More file actions
256 lines (212 loc) · 11 KB
/
KeychainWrapper.swift
File metadata and controls
256 lines (212 loc) · 11 KB
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
//
// KeychainWrapper.swift
// KeychainWrapper
//
// Created by Jason Rendel on 9/23/14.
// Copyright (c) 2014 Jason Rendel. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import Foundation
let SecMatchLimit: String! = kSecMatchLimit as String
let SecReturnData: String! = kSecReturnData as String
let SecValueData: String! = kSecValueData as String
let SecAttrAccessible: String! = kSecAttrAccessible as String
let SecClass: String! = kSecClass as String
let SecAttrService: String! = kSecAttrService as String
let SecAttrGeneric: String! = kSecAttrGeneric as String
let SecAttrAccount: String! = kSecAttrAccount as String
let SecAttrAccessGroup: String! = kSecAttrAccessGroup as String
/// 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.
public class KeychainWrapper {
// MARK: Private static Properties
private struct internalVars {
static var serviceName: String = ""
static var accessGroup: String = ""
}
// MARK: Public Properties
/// 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.
///
///This is a static property and only needs to be set once
public class var serviceName: String {
get {
if internalVars.serviceName.isEmpty {
internalVars.serviceName = Bundle.main().bundleIdentifier ?? "SwiftKeychainWrapper"
}
return internalVars.serviceName
}
set(newServiceName) {
internalVars.serviceName = newServiceName
}
}
/// 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.
///
/// Access Group defaults to an empty string and is not used until a valid value is set.
///
/// This is a static property and only needs to be set once. To remove the access group property after one has been set, set this to an empty string.
public class var accessGroup: String {
get {
return internalVars.accessGroup
}
set(newAccessGroup){
internalVars.accessGroup = newAccessGroup
}
}
// MARK: Public Methods
/// Checks if keychain data exists for a specified key.
///
/// :param: keyName The key to check for.
/// :returns: True if a value exists for the key. False otherwise.
public class func hasValueForKey(keyName: String) -> Bool {
let keychainData: NSData? = self.dataForKey(keyName: keyName)
if keychainData != nil {
return true
} else {
return false
}
}
/// Returns a string value for a specified key.
///
/// :param: keyName The key to lookup data for.
/// :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.
public class func stringForKey(keyName: String) -> String? {
let keychainData: NSData? = self.dataForKey(keyName: keyName)
var stringValue: String?
if let data = keychainData {
stringValue = NSString(data: data as Data, encoding: String.Encoding.utf8.rawValue) as String?
}
return stringValue
}
/// Returns an object that conforms to NSCoding for a specified key.
///
/// :param: keyName The key to lookup data for.
/// :returns: The decoded object associated with the key if it exists. If no data exists, or the data found cannot be decoded, returns nil.
public class func objectForKey(keyName: String) -> NSCoding? {
let dataValue: NSData? = self.dataForKey(keyName: keyName)
var objectValue: NSCoding?
if let data = dataValue {
objectValue = NSKeyedUnarchiver.unarchiveObject(with: data as Data) as? NSCoding
}
return objectValue;
}
/// Returns a NSData object for a specified key.
///
/// :param: keyName The key to lookup data for.
/// :returns: The NSData object associated with the key if it exists. If no data exists, returns nil.
public class func dataForKey(keyName: String) -> NSData? {
var keychainQueryDictionary = self.setupKeychainQueryDictionaryForKey(keyName: keyName)
var result: AnyObject?
// Limit search results to one
keychainQueryDictionary[SecMatchLimit] = kSecMatchLimitOne
// Specify we want NSData/CFData returned
keychainQueryDictionary[SecReturnData] = kCFBooleanTrue
// Search
let status = withUnsafeMutablePointer(&result) {
SecItemCopyMatching(keychainQueryDictionary, UnsafeMutablePointer($0))
}
return status == noErr ? result as? NSData : nil
}
/// Save a String value to the keychain associated with a specified key. If a String value already exists for the given keyname, the string will be overwritten with the new value.
///
/// :param: value The String value to save.
/// :param: forKey The key to save the String under.
/// :returns: True if the save was successful, false otherwise.
public class func setString(value: String, forKey keyName: String) -> Bool {
if let data = value.data(using: String.Encoding.utf8) {
return self.setData(value: data, forKey: keyName)
} else {
return false
}
}
/// Save an NSCoding compliant object to the keychain associated with a specified key. If an object already exists for the given keyname, the object will be overwritten with the new value.
///
/// :param: value The NSCoding compliant object to save.
/// :param: forKey The key to save the object under.
/// :returns: True if the save was successful, false otherwise.
public class func setObject(value: NSCoding, forKey keyName: String) -> Bool {
let data = NSKeyedArchiver.archivedData(withRootObject: value)
return self.setData(value: data, forKey: keyName)
}
/// Save a NSData object to the keychain associated with a specified key. If data already exists for the given keyname, the data will be overwritten with the new value.
///
/// :param: value The NSData object to save.
/// :param: forKey The key to save the object under.
/// :returns: True if the save was successful, false otherwise.
public class func setData(value: NSData, forKey keyName: String) -> Bool {
var keychainQueryDictionary: [String:AnyObject] = self.setupKeychainQueryDictionaryForKey(keyName: keyName)
keychainQueryDictionary[SecValueData] = value
// Protect the keychain entry so it's only valid when the device is unlocked
keychainQueryDictionary[SecAttrAccessible] = kSecAttrAccessibleWhenUnlocked
let status: OSStatus = SecItemAdd(keychainQueryDictionary, nil)
if status == errSecSuccess {
return true
} else if status == errSecDuplicateItem {
return self.updateData(value: value, forKey: keyName)
} else {
return false
}
}
/// Remove an object associated with a specified key.
///
/// :param: keyName The key value to remove data for.
/// :returns: True if successful, false otherwise.
public class func removeObjectForKey(keyName: String) -> Bool {
let keychainQueryDictionary: [String:AnyObject] = self.setupKeychainQueryDictionaryForKey(keyName: keyName)
// Delete
let status: OSStatus = SecItemDelete(keychainQueryDictionary);
if status == errSecSuccess {
return true
} else {
return false
}
}
// MARK: Private Methods
/// Update existing data associated with a specified key name. The existing data will be overwritten by the new data
private class func updateData(value: NSData, forKey keyName: String) -> Bool {
let keychainQueryDictionary: [String:AnyObject] = self.setupKeychainQueryDictionaryForKey(keyName: keyName)
let updateDictionary = [SecValueData:value]
// Update
let status: OSStatus = SecItemUpdate(keychainQueryDictionary, updateDictionary)
if status == errSecSuccess {
return true
} else {
return false
}
}
/// 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.
///
/// :param: keyName The key this query is for
/// :returns: A dictionary with all the needed properties setup to access the keychain on iOS
private class func setupKeychainQueryDictionaryForKey(keyName: String) -> [String:AnyObject] {
// Setup dictionary to access keychain and specify we are using a generic password (rather than a certificate, internet password, etc)
var keychainQueryDictionary: [String:AnyObject] = [SecClass:kSecClassGenericPassword]
// Uniquely identify this keychain accessor
keychainQueryDictionary[SecAttrService] = KeychainWrapper.serviceName
// Set the keychain access group if defined
if !KeychainWrapper.accessGroup.isEmpty {
keychainQueryDictionary[SecAttrAccessGroup] = KeychainWrapper.accessGroup
}
// Uniquely identify the account who will be accessing the keychain
let encodedIdentifier: NSData? = keyName.data(using: String.Encoding.utf8)
keychainQueryDictionary[SecAttrGeneric] = encodedIdentifier
keychainQueryDictionary[SecAttrAccount] = encodedIdentifier
return keychainQueryDictionary
}
}