BaseKeychain.swift 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  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. keys.update(with: key)
  77. }
  78. }
  79. }
  80. return keys
  81. }
  82. func hasValue(forKey key: String) -> Result<Bool, KeychainError> {
  83. getData(forKey: key).map { $0 != nil }
  84. }
  85. func accessibilityOfKey(_ key: String) -> Result<KeychainItemAccessibility, KeychainError> {
  86. var keychainQueryDictionary = setupKeychainQueryDictionary(
  87. forKey: key,
  88. synchronizable: defaultSynchronizable.keychainFlag,
  89. withAccessibility: defaultAccessibilityLevel
  90. )
  91. var result: AnyObject?
  92. // Remove accessibility attribute
  93. keychainQueryDictionary.removeValue(forKey: SecAttrAccessible)
  94. // Limit search results to one
  95. keychainQueryDictionary[SecMatchLimit] = kSecMatchLimitOne
  96. // Specify we want SecAttrAccessible returned
  97. keychainQueryDictionary[SecReturnAttributes] = kCFBooleanTrue
  98. // Search
  99. let status = withUnsafeMutablePointer(to: &result) {
  100. SecItemCopyMatching(keychainQueryDictionary as CFDictionary, UnsafeMutablePointer($0))
  101. }
  102. if status == errSecSuccess {
  103. if let resultsDictionary = result as? [String: AnyObject],
  104. let accessibilityAttrValue = resultsDictionary[SecAttrAccessible] as? String,
  105. let mappedValue = KeychainItemAccessibility.accessibilityForAttributeValue(accessibilityAttrValue as CFString)
  106. {
  107. return .success(mappedValue)
  108. }
  109. }
  110. return .failure(.darwinError(status))
  111. }
  112. // MARK: Public Getters
  113. func getData(forKey key: String) -> Result<Data?, KeychainError> {
  114. var keychainQueryDictionary = setupKeychainQueryDictionary(
  115. forKey: key,
  116. synchronizable: defaultSynchronizable.keychainFlag,
  117. withAccessibility: defaultAccessibilityLevel
  118. )
  119. var result: AnyObject?
  120. // Limit search results to one
  121. keychainQueryDictionary[SecMatchLimit] = kSecMatchLimitOne
  122. // Specify we want Data/CFData returned
  123. keychainQueryDictionary[SecReturnData] = kCFBooleanTrue
  124. // Search
  125. let status = withUnsafeMutablePointer(to: &result) {
  126. SecItemCopyMatching(keychainQueryDictionary as CFDictionary, UnsafeMutablePointer($0))
  127. }
  128. if status == errSecSuccess {
  129. return .success(result as? Data)
  130. } else if status == errSecItemNotFound {
  131. return .success(nil)
  132. }
  133. return .failure(.darwinError(status))
  134. }
  135. func getValue<T: Decodable>(_: T.Type, forKey key: String) -> Result<T?, KeychainError> {
  136. switch getData(forKey: key) {
  137. case let .success(data):
  138. guard let data = data else { return .success(nil) }
  139. let decoder = JSONDecoder()
  140. do {
  141. let decoded = try decoder.decode(DecodableWrapper<T>.self, from: data)
  142. return .success(decoded.v)
  143. } catch {
  144. return .failure(.codingError(error))
  145. }
  146. case let .failure(error):
  147. return .failure(error)
  148. }
  149. }
  150. // MARK: Public Setters
  151. @discardableResult
  152. 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
  170. func setValue<T: Encodable>(_ maybeValue: T?, forKey key: String) -> Result<Void, KeychainError> {
  171. if let value = maybeValue {
  172. let wrapper = EncodableWrapper(v: value)
  173. let encoder = JSONEncoder()
  174. do {
  175. let encoded = try encoder.encode(wrapper)
  176. return setData(encoded, forKey: key)
  177. } catch {
  178. return .failure(.codingError(error))
  179. }
  180. } else {
  181. return removeObject(forKey: key)
  182. }
  183. }
  184. private func removeObject(
  185. forKey key: String,
  186. withAccessibility accessibility: KeychainItemAccessibility? = nil
  187. ) -> Result<Void, KeychainError> {
  188. let keychainQueryDictionary: [String: Any] = setupKeychainQueryDictionary(
  189. forKey: key,
  190. synchronizable: .any,
  191. withAccessibility: accessibility ?? defaultAccessibilityLevel
  192. )
  193. // Delete
  194. let status = SecItemDelete(keychainQueryDictionary as CFDictionary)
  195. if status == errSecSuccess || status == errSecItemNotFound {
  196. return .success(())
  197. } else {
  198. return .failure(.darwinError(status))
  199. }
  200. }
  201. @discardableResult
  202. func removeObject(forKey key: String) -> Result<Void, KeychainError> {
  203. removeObject(forKey: key, withAccessibility: defaultAccessibilityLevel)
  204. }
  205. /// Remove all keychain data added through KeychainWrapper. This will only delete items matching the currnt ServiceName and AccessGroup if one is set.
  206. func removeAllKeys() -> Result<Void, KeychainError> {
  207. // Setup dictionary to access keychain and specify we are using a generic password (rather than a certificate, internet password, etc)
  208. var keychainQueryDictionary: [String: Any] = [SecClass: kSecClassGenericPassword]
  209. // Uniquely identify this keychain accessor
  210. keychainQueryDictionary[SecAttrService] = serviceName
  211. keychainQueryDictionary[SecAttrSynchronizable] = SecAttrSynchronizableAny
  212. // Set the keychain access group if defined
  213. if let accessGroup = self.accessGroup {
  214. keychainQueryDictionary[SecAttrAccessGroup] = accessGroup
  215. }
  216. let status = SecItemDelete(keychainQueryDictionary as CFDictionary)
  217. if status == errSecSuccess || status == errSecItemNotFound {
  218. return .success(())
  219. } else {
  220. return .failure(.darwinError(status))
  221. }
  222. }
  223. /// Remove all keychain data, including data not added through keychain wrapper.
  224. ///
  225. /// - Warning: This may remove custom keychain entries you did not add via SwiftKeychainWrapper.
  226. ///
  227. static func wipeKeychain() {
  228. deleteKeychainSecClass(kSecClassGenericPassword) // Generic password items
  229. deleteKeychainSecClass(kSecClassInternetPassword) // Internet password items
  230. deleteKeychainSecClass(kSecClassCertificate) // Certificate items
  231. deleteKeychainSecClass(kSecClassKey) // Cryptographic key items
  232. deleteKeychainSecClass(kSecClassIdentity) // Identity items
  233. }
  234. // MARK: - Private Methods
  235. /// Remove all items for a given Keychain Item Class
  236. ///
  237. ///
  238. @discardableResult
  239. private class func deleteKeychainSecClass(_ secClass: AnyObject) -> Result<Void, KeychainError> {
  240. let query = [SecClass: secClass]
  241. let status = SecItemDelete(query as CFDictionary)
  242. if status == errSecSuccess {
  243. return .success(())
  244. } else {
  245. return .failure(.darwinError(status))
  246. }
  247. }
  248. /// Update existing data associated with a specified key name. The existing data will be overwritten by the new data
  249. private func update(_ value: Data, forKey key: String) -> Result<Void, KeychainError> {
  250. var keychainQueryDictionary: [String: Any] = setupKeychainQueryDictionary(
  251. forKey: key,
  252. synchronizable: defaultSynchronizable.keychainFlag,
  253. withAccessibility: defaultAccessibilityLevel
  254. )
  255. let updateDictionary = [SecValueData: value]
  256. keychainQueryDictionary[SecAttrAccessible] = defaultAccessibilityLevel.keychainAttrValue
  257. // Update
  258. let status = SecItemUpdate(keychainQueryDictionary as CFDictionary, updateDictionary as CFDictionary)
  259. if status == errSecSuccess {
  260. return .success(())
  261. } else {
  262. return .failure(.darwinError(status))
  263. }
  264. }
  265. private func setupKeychainQueryDictionary(
  266. forKey key: String,
  267. synchronizable: KeychainSynchronizable,
  268. withAccessibility accessibility: KeychainItemAccessibility
  269. ) -> [String: Any] {
  270. // Setup default access as generic password (rather than a certificate, internet password, etc)
  271. var keychainQueryDictionary: [String: Any] = [SecClass: kSecClassGenericPassword]
  272. // Uniquely identify this keychain accessor
  273. keychainQueryDictionary[SecAttrService] = serviceName
  274. keychainQueryDictionary[SecAttrAccessible] = accessibility.keychainAttrValue
  275. // Set the keychain access group if defined
  276. if let accessGroup = self.accessGroup {
  277. keychainQueryDictionary[SecAttrAccessGroup] = accessGroup
  278. }
  279. // Uniquely identify the account who will be accessing the keychain
  280. let encodedIdentifier: Data? = key.data(using: String.Encoding.utf8)
  281. keychainQueryDictionary[SecAttrGeneric] = encodedIdentifier
  282. keychainQueryDictionary[SecAttrAccount] = encodedIdentifier
  283. keychainQueryDictionary[SecAttrSynchronizable] = { () -> Any in
  284. switch synchronizable {
  285. case .yes: return true
  286. case .no: return false
  287. case .any: return SecAttrSynchronizableAny
  288. }
  289. }()
  290. return keychainQueryDictionary
  291. }
  292. }
  293. private extension Bool {
  294. var keychainFlag: BaseKeychain.KeychainSynchronizable {
  295. switch self {
  296. case true: return .yes
  297. case false: return .no
  298. }
  299. }
  300. }
  301. extension BaseKeychain: KeyValueStorage {
  302. func getValue<T: Codable>(_: T.Type, forKey key: String) -> T? {
  303. getValue(T.self, forKey: key, defaultValue: nil, reportError: true)
  304. }
  305. func getValue<T: Codable>(_: T.Type, forKey key: String, defaultValue: T?, reportError: Bool) -> T? {
  306. let result = getValue(T.self, forKey: key) as Result<T?, KeychainError>
  307. if reportError, case let .failure(error) = result {
  308. assertionFailure("Failed to set persisted value for key: \(key), error: \(error.localizedDescription)")
  309. }
  310. return try? result.get() ?? defaultValue
  311. }
  312. func setValue<T: Codable>(_ maybeValue: T?, forKey key: String) {
  313. setValue(maybeValue, forKey: key, reportError: true)
  314. }
  315. func setValue<T: Codable>(_ maybeValue: T?, forKey key: String, reportError: Bool) {
  316. let result = setValue(maybeValue, forKey: key) as Result<Void, KeychainError>
  317. if reportError, case let .failure(error) = result {
  318. assertionFailure("Failed to set persisted value.for key: \(key), error: \(error.localizedDescription)")
  319. }
  320. }
  321. }