Created
June 9, 2021 14:27
-
-
Save guitaripod/37d4d7d28a236711aa0fe2ec0942f4b8 to your computer and use it in GitHub Desktop.
Revisions
-
guitaripod created this gist
Jun 9, 2021 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,142 @@ import Foundation struct KeychainItem { // MARK: Types enum KeychainError: Error { case noPassword case unexpectedPasswordData case unexpectedItemData case unhandledError } // MARK: Properties let service: String private(set) var account: String let accessGroup: String? // MARK: Intialization init(service: String, account: String, accessGroup: String? = nil) { self.service = service self.account = account self.accessGroup = accessGroup } // MARK: Keychain access func readItem() throws -> String { /* Build a query to find the item that matches the service, account and access group. */ var query = KeychainItem.keychainQuery(withService: service, account: account, accessGroup: accessGroup) query[kSecMatchLimit as String] = kSecMatchLimitOne query[kSecReturnAttributes as String] = kCFBooleanTrue query[kSecReturnData as String] = kCFBooleanTrue // Try to fetch the existing keychain item that matches the query. var queryResult: AnyObject? let status = withUnsafeMutablePointer(to: &queryResult) { SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) } // Check the return status and throw an error if appropriate. guard status != errSecItemNotFound else { throw KeychainError.noPassword } guard status == noErr else { throw KeychainError.unhandledError } // Parse the password string from the query result. guard let existingItem = queryResult as? [String: AnyObject], let passwordData = existingItem[kSecValueData as String] as? Data, let password = String(data: passwordData, encoding: String.Encoding.utf8) else { throw KeychainError.unexpectedPasswordData } return password } func saveItem(_ password: String) throws { // Encode the password into an Data object. let encodedPassword = password.data(using: String.Encoding.utf8)! do { // Check for an existing item in the keychain. try _ = readItem() // Update the existing item with the new password. var attributesToUpdate = [String: AnyObject]() attributesToUpdate[kSecValueData as String] = encodedPassword as AnyObject? let query = KeychainItem.keychainQuery(withService: service, account: account, accessGroup: accessGroup) let status = SecItemUpdate(query as CFDictionary, attributesToUpdate as CFDictionary) // Throw an error if an unexpected status was returned. guard status == noErr else { throw KeychainError.unhandledError } } catch KeychainError.noPassword { /* No password was found in the keychain. Create a dictionary to save as a new keychain item. */ var newItem = KeychainItem.keychainQuery(withService: service, account: account, accessGroup: accessGroup) newItem[kSecValueData as String] = encodedPassword as AnyObject? // Add a the new item to the keychain. let status = SecItemAdd(newItem as CFDictionary, nil) // Throw an error if an unexpected status was returned. guard status == noErr else { throw KeychainError.unhandledError } } } func deleteItem() throws { // Delete the existing item from the keychain. let query = KeychainItem.keychainQuery(withService: service, account: account, accessGroup: accessGroup) let status = SecItemDelete(query as CFDictionary) // Throw an error if an unexpected status was returned. guard status == noErr || status == errSecItemNotFound else { throw KeychainError.unhandledError } } // MARK: Convenience private static func keychainQuery(withService service: String, account: String? = nil, accessGroup: String? = nil) -> [String: AnyObject] { var query = [String: AnyObject]() query[kSecClass as String] = kSecClassGenericPassword query[kSecAttrService as String] = service as AnyObject? if let account = account { query[kSecAttrAccount as String] = account as AnyObject? } if let accessGroup = accessGroup { query[kSecAttrAccessGroup as String] = accessGroup as AnyObject? } return query } /* For the purpose of this demo app, the user identifier will be stored in the device keychain. You should store the user identifier in your account management system. */ static var currentUserIdentifier: String { do { let storedIdentifier = try KeychainItem(service: "com.example.apple-samplecode.juice", account: "userIdentifier").readItem() return storedIdentifier } catch { return "" } } static func deleteUserIdentifierFromKeychain() { do { try KeychainItem(service: "com.example.apple-samplecode.juice", account: "userIdentifier").deleteItem() } catch { print("Unable to delete userIdentifier from keychain") } } }