BaseKeychain.swift 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. import Foundation
  2. import Security
  3. private let SecAttrAccessGroup = kSecAttrAccessGroup as String
  4. private let SecAttrAccessible = kSecAttrAccessible as String
  5. private let SecAttrAccount = kSecAttrAccount as String
  6. private let SecAttrGeneric = kSecAttrGeneric as String
  7. private let SecAttrService = kSecAttrService as String
  8. private let SecAttrSynchronizable = kSecAttrSynchronizable as String
  9. private let SecAttrSynchronizableAny = kSecAttrSynchronizableAny as String
  10. private let SecClass = kSecClass as String
  11. private let SecMatchLimit = kSecMatchLimit as String
  12. private let SecReturnAttributes = kSecReturnAttributes as String
  13. private let SecReturnData = kSecReturnData as String
  14. private let SecReturnPersistentRef = kSecReturnPersistentRef as String
  15. private let SecValueData = kSecValueData as String
  16. /// 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.
  17. final class BaseKeychain: Keychain {
  18. enum Config {
  19. static let defaultAccessibilityLevel = KeychainItemAccessibility.afterFirstUnlock
  20. static let defaultSynchronizable = true
  21. }
  22. fileprivate enum KeychainSynchronizable {
  23. case any
  24. case yes
  25. case no
  26. }
  27. private struct EncodableWrapper<T: Encodable>: Encodable {
  28. let v: T
  29. }
  30. private struct DecodableWrapper<T: Decodable>: Decodable {
  31. let v: T
  32. }
  33. /// 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.
  34. private(set) var serviceName: String
  35. /// 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.
  36. private(set) var accessGroup: String?
  37. private let defaultSynchronizable: Bool
  38. private let defaultAccessibilityLevel: KeychainItemAccessibility
  39. private static let defaultServiceName: String = {
  40. Bundle.main.bundleIdentifier ?? "SwiftBaseKeychain"
  41. }()
  42. init(
  43. serviceName: String = BaseKeychain.defaultServiceName,
  44. synchronizable: Bool = Config.defaultSynchronizable,
  45. accessibilityLevel: KeychainItemAccessibility = Config.defaultAccessibilityLevel,
  46. accessGroup: String? = nil
  47. ) {
  48. self.serviceName = serviceName
  49. defaultSynchronizable = synchronizable
  50. defaultAccessibilityLevel = accessibilityLevel
  51. self.accessGroup = accessGroup
  52. }
  53. // MARK: - Public Methods
  54. func allKeys() -> Set<String> {
  55. var query: [String: Any] = [SecClass: kSecClassGenericPassword]
  56. query[SecAttrService] = serviceName
  57. query[SecMatchLimit] = kSecMatchLimitAll
  58. query[SecReturnAttributes] = kCFBooleanTrue
  59. query[SecReturnData] = kCFBooleanTrue
  60. query[SecAttrSynchronizable] = kSecAttrSynchronizableAny
  61. if let accessGroup = accessGroup {
  62. query[SecAttrAccessGroup] = accessGroup
  63. }
  64. var result: AnyObject?
  65. let lastResultCode = withUnsafeMutablePointer(to: &result) {
  66. SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
  67. }
  68. var keys = Set<String>()
  69. if lastResultCode == noErr {
  70. guard let array = result as? [[String: Any]] else {
  71. return keys
  72. }
  73. for item in array {
  74. if let keyData = item[SecAttrAccount] as? Data,
  75. let key = String(data: keyData, encoding: .utf8)
  76. {
  77. keys.update(with: key)
  78. }
  79. }
  80. }
  81. return keys
  82. }
  83. func hasValue(forKey key: String) -> Result<Bool, KeychainError> {
  84. getData(forKey: key).map { $0 != nil }
  85. }
  86. func accessibilityOfKey(_ key: String) -> Result<KeychainItemAccessibility, KeychainError> {
  87. var keychainQueryDictionary = setupKeychainQueryDictionary(
  88. forKey: key,
  89. synchronizable: defaultSynchronizable.keychainFlag,
  90. withAccessibility: defaultAccessibilityLevel
  91. )
  92. var result: AnyObject?
  93. // Remove accessibility attribute
  94. keychainQueryDictionary.removeValue(forKey: SecAttrAccessible)
  95. // Limit search results to one
  96. keychainQueryDictionary[SecMatchLimit] = kSecMatchLimitOne
  97. // Specify we want SecAttrAccessible returned
  98. keychainQueryDictionary[SecReturnAttributes] = kCFBooleanTrue
  99. // Search
  100. let status = withUnsafeMutablePointer(to: &result) {
  101. SecItemCopyMatching(keychainQueryDictionary as CFDictionary, UnsafeMutablePointer($0))
  102. }
  103. if status == errSecSuccess {
  104. if let resultsDictionary = result as? [String: AnyObject],
  105. let accessibilityAttrValue = resultsDictionary[SecAttrAccessible] as? String,
  106. let mappedValue = KeychainItemAccessibility.accessibilityForAttributeValue(accessibilityAttrValue as CFString)
  107. {
  108. return .success(mappedValue)
  109. }
  110. }
  111. return .failure(.darwinError(status))
  112. }
  113. // MARK: Public Getters
  114. func getData(forKey key: String) -> Result<Data?, KeychainError> {
  115. var keychainQueryDictionary = setupKeychainQueryDictionary(
  116. forKey: key,
  117. synchronizable: defaultSynchronizable.keychainFlag,
  118. withAccessibility: defaultAccessibilityLevel
  119. )
  120. var result: AnyObject?
  121. // Limit search results to one
  122. keychainQueryDictionary[SecMatchLimit] = kSecMatchLimitOne
  123. // Specify we want Data/CFData returned
  124. keychainQueryDictionary[SecReturnData] = kCFBooleanTrue
  125. // Search
  126. let status = withUnsafeMutablePointer(to: &result) {
  127. SecItemCopyMatching(keychainQueryDictionary as CFDictionary, UnsafeMutablePointer($0))
  128. }
  129. if status == errSecSuccess {
  130. return .success(result as? Data)
  131. } else if status == errSecItemNotFound {
  132. return .success(nil)
  133. }
  134. return .failure(.darwinError(status))
  135. }
  136. func getValue<T: Decodable>(_: T.Type, forKey key: String) -> Result<T?, KeychainError> {
  137. switch getData(forKey: key) {
  138. case let .success(data):
  139. guard let data = data else { return .success(nil) }
  140. let decoder = JSONDecoder()
  141. do {
  142. let decoded = try decoder.decode(DecodableWrapper<T>.self, from: data)
  143. return .success(decoded.v)
  144. } catch {
  145. return .failure(.codingError(error))
  146. }
  147. case let .failure(error):
  148. return .failure(error)
  149. }
  150. }
  151. // MARK: Public Setters
  152. @discardableResult func setData(_ value: Data, forKey key: String) -> Result<Void, KeychainError> {
  153. var keychainQueryDictionary: [String: Any] = setupKeychainQueryDictionary(
  154. forKey: key,
  155. synchronizable: defaultSynchronizable.keychainFlag,
  156. withAccessibility: defaultAccessibilityLevel
  157. )
  158. keychainQueryDictionary[SecValueData] = value
  159. keychainQueryDictionary[SecAttrAccessible] = defaultAccessibilityLevel.keychainAttrValue
  160. let status = SecItemAdd(keychainQueryDictionary as CFDictionary, nil)
  161. if status == errSecSuccess {
  162. return .success(())
  163. } else if status == errSecDuplicateItem {
  164. return update(value, forKey: key)
  165. } else {
  166. return .failure(.darwinError(status))
  167. }
  168. }
  169. @discardableResult func setValue<T: Encodable>(_ maybeValue: T?, forKey key: String) -> Result<Void, KeychainError> {
  170. if let value = maybeValue {
  171. let wrapper = EncodableWrapper(v: value)
  172. let encoder = JSONEncoder()
  173. do {
  174. let encoded = try encoder.encode(wrapper)
  175. return setData(encoded, forKey: key)
  176. } catch {
  177. return .failure(.codingError(error))
  178. }
  179. } else {
  180. return removeObject(forKey: key)
  181. }
  182. }
  183. private func removeObject(
  184. forKey key: String,
  185. withAccessibility accessibility: KeychainItemAccessibility? = nil
  186. ) -> Result<Void, KeychainError> {
  187. let keychainQueryDictionary: [String: Any] = setupKeychainQueryDictionary(
  188. forKey: key,
  189. synchronizable: .any,
  190. withAccessibility: accessibility ?? defaultAccessibilityLevel
  191. )
  192. // Delete
  193. let status = SecItemDelete(keychainQueryDictionary as CFDictionary)
  194. if status == errSecSuccess || status == errSecItemNotFound {
  195. return .success(())
  196. } else {
  197. return .failure(.darwinError(status))
  198. }
  199. }
  200. @discardableResult func removeObject(forKey key: String) -> Result<Void, KeychainError> {
  201. removeObject(forKey: key, withAccessibility: defaultAccessibilityLevel)
  202. }
  203. /// Remove all keychain data added through KeychainWrapper. This will only delete items matching the currnt ServiceName and AccessGroup if one is set.
  204. func removeAllKeys() -> Result<Void, KeychainError> {
  205. // Setup dictionary to access keychain and specify we are using a generic password (rather than a certificate, internet password, etc)
  206. var keychainQueryDictionary: [String: Any] = [SecClass: kSecClassGenericPassword]
  207. // Uniquely identify this keychain accessor
  208. keychainQueryDictionary[SecAttrService] = serviceName
  209. keychainQueryDictionary[SecAttrSynchronizable] = SecAttrSynchronizableAny
  210. // Set the keychain access group if defined
  211. if let accessGroup = self.accessGroup {
  212. keychainQueryDictionary[SecAttrAccessGroup] = accessGroup
  213. }
  214. let status = SecItemDelete(keychainQueryDictionary as CFDictionary)
  215. if status == errSecSuccess || status == errSecItemNotFound {
  216. return .success(())
  217. } else {
  218. return .failure(.darwinError(status))
  219. }
  220. }
  221. /// Remove all keychain data, including data not added through keychain wrapper.
  222. ///
  223. /// - Warning: This may remove custom keychain entries you did not add via SwiftKeychainWrapper.
  224. ///
  225. static func wipeKeychain() {
  226. deleteKeychainSecClass(kSecClassGenericPassword) // Generic password items
  227. deleteKeychainSecClass(kSecClassInternetPassword) // Internet password items
  228. deleteKeychainSecClass(kSecClassCertificate) // Certificate items
  229. deleteKeychainSecClass(kSecClassKey) // Cryptographic key items
  230. deleteKeychainSecClass(kSecClassIdentity) // Identity items
  231. }
  232. // MARK: - Private Methods
  233. /// Remove all items for a given Keychain Item Class
  234. ///
  235. ///
  236. @discardableResult private class func deleteKeychainSecClass(_ secClass: AnyObject) -> Result<Void, KeychainError> {
  237. let query = [SecClass: secClass]
  238. let status = SecItemDelete(query as CFDictionary)
  239. if status == errSecSuccess {
  240. return .success(())
  241. } else {
  242. return .failure(.darwinError(status))
  243. }
  244. }
  245. /// Update existing data associated with a specified key name. The existing data will be overwritten by the new data
  246. private func update(_ value: Data, forKey key: String) -> Result<Void, KeychainError> {
  247. var keychainQueryDictionary: [String: Any] = setupKeychainQueryDictionary(
  248. forKey: key,
  249. synchronizable: defaultSynchronizable.keychainFlag,
  250. withAccessibility: defaultAccessibilityLevel
  251. )
  252. let updateDictionary = [SecValueData: value]
  253. keychainQueryDictionary[SecAttrAccessible] = defaultAccessibilityLevel.keychainAttrValue
  254. // Update
  255. let status = SecItemUpdate(keychainQueryDictionary as CFDictionary, updateDictionary as CFDictionary)
  256. if status == errSecSuccess {
  257. return .success(())
  258. } else {
  259. return .failure(.darwinError(status))
  260. }
  261. }
  262. private func setupKeychainQueryDictionary(
  263. forKey key: String,
  264. synchronizable: KeychainSynchronizable,
  265. withAccessibility accessibility: KeychainItemAccessibility
  266. ) -> [String: Any] {
  267. // Setup default access as generic password (rather than a certificate, internet password, etc)
  268. var keychainQueryDictionary: [String: Any] = [SecClass: kSecClassGenericPassword]
  269. // Uniquely identify this keychain accessor
  270. keychainQueryDictionary[SecAttrService] = serviceName
  271. keychainQueryDictionary[SecAttrAccessible] = accessibility.keychainAttrValue
  272. // Set the keychain access group if defined
  273. if let accessGroup = self.accessGroup {
  274. keychainQueryDictionary[SecAttrAccessGroup] = accessGroup
  275. }
  276. // Uniquely identify the account who will be accessing the keychain
  277. let encodedIdentifier: Data? = key.data(using: String.Encoding.utf8)
  278. keychainQueryDictionary[SecAttrGeneric] = encodedIdentifier
  279. keychainQueryDictionary[SecAttrAccount] = encodedIdentifier
  280. keychainQueryDictionary[SecAttrSynchronizable] = { () -> Any in
  281. switch synchronizable {
  282. case .yes: return true
  283. case .no: return false
  284. case .any: return SecAttrSynchronizableAny
  285. }
  286. }()
  287. return keychainQueryDictionary
  288. }
  289. }
  290. private extension Bool {
  291. var keychainFlag: BaseKeychain.KeychainSynchronizable {
  292. switch self {
  293. case true: return .yes
  294. case false: return .no
  295. }
  296. }
  297. }
  298. extension BaseKeychain: KeyValueStorage {
  299. func getValue<T: Codable>(_: T.Type, forKey key: String) -> T? {
  300. getValue(T.self, forKey: key, defaultValue: nil, reportError: true)
  301. }
  302. func getValue<T: Codable>(_: T.Type, forKey key: String, defaultValue: T?, reportError: Bool) -> T? {
  303. let result = getValue(T.self, forKey: key) as Result<T?, KeychainError>
  304. if reportError, case let .failure(error) = result {
  305. assertionFailure("Failed to set persisted value for key: \(key), error: \(error.localizedDescription)")
  306. }
  307. return try? result.get() ?? defaultValue
  308. }
  309. func setValue<T: Codable>(_ maybeValue: T?, forKey key: String) {
  310. setValue(maybeValue, forKey: key, reportError: true)
  311. }
  312. func setValue<T: Codable>(_ maybeValue: T?, forKey key: String, reportError: Bool) {
  313. let result = setValue(maybeValue, forKey: key) as Result<Void, KeychainError>
  314. if reportError, case let .failure(error) = result {
  315. assertionFailure("Failed to set persisted value.for key: \(key), error: \(error.localizedDescription)")
  316. }
  317. }
  318. }