I am working on an iOS/iPadOS application, written in Swift and Obj-C, using both UIKit and SwiftUI.
I am attempting to save an item to the keychain using the following code:
@objc class UserPin : NSObject {
@objc var uuid: UUID = UUID()
@objc var PIN: String = ""
@objc convenience init(uuid: UUID, PIN: String){
self.init()
self.uuid = uuid
self.PIN = PIN
}
}
@objc func saveUserPin(userPin: UserPin){
self.deleteUserPinForUser(uuid: userPin.uuid)
do {
try KeychainAccessorService.storeItem(keyName: kUserPin, username: userPin.uuid.uuidString.data(using: .utf8)!, password: EncryptionHelper.encryptString(stringToEncrypt: userPin.PIN)!)
} catch {
print(error)
}
}
static func storeItem(keyName: String, username: Data, password: Data, otherData: Data? = nil) throws {
var query = [kSecClass: kSecClassGenericPassword,
kSecAttrLabel: keyName,
kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
kSecAttrIsInvisible: true,
kSecAttrSynchronizable: false,
kSecUseDataProtectionKeychain: true,
kSecValueData: password,
kSecAttrAccount: username] as [String: Any]
if otherData != nil {
query[kSecAttrGeneric as String] = otherData
}
// Add the key data.
let status = SecItemAdd(query as CFDictionary, nil)
guard status == errSecSuccess else {
throw TransnetError("Unable to store item: \(status.message)")
}
}
Upon executing this code, I get the error:
Unable to store item: The specified item already exists in the keychain.
This code returns that an item is not present with these criteria (I tried with both the KSecAttrAccount commented out and in, it does not appear to matter either way. It returns status code -25300.
@objc func deleteUserPinForUser(uuid: UUID){
var query = getDeletionDictionaryForTag(tag: kUserPin)
// query[kSecAttrAccount as String] = uuid.uuidString.data(using: .utf8)
let _ = SecItemDelete((query as CFDictionary))
}
private func getDeletionDictionaryForTag(tag: String) -> [String: Any]{
let dict = [kSecClass as String: kSecClassGenericPassword,
kSecAttrLabel as String: tag,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked,
kSecAttrIsInvisible as String: true,
kSecAttrSynchronizable as String: false,
kSecUseDataProtectionKeychain as String: true] as [String : Any]
return dict;
}
I am also not able to retrieve this item using the following code:
@objc func fetchUserPin(uuid: UUID) -> UserPin{
let userPin = UserPin()
do {
let itemDictionary = try KeychainAccessorService.fetchItemForTag(name: kUserPin, username: uuid.uuidString.data(using: .utf8))
let passwordData = EncryptionHelper.decryptString(dataToDecrypt: itemDictionary?[kSecValueData as String] ?? Data()) ?? ""
userPin.uuid = uuid
userPin.PIN = passwordData
} catch {
}
return userPin
}
private static func fetchItemForTag(name: String, username: Data? = nil) throws -> Dictionary<String, Data>? {
// Seek a generic password with the given account.
var query = getFetchDictionary(name: name, matchLimit: kSecMatchLimitOne as String)
if let username = username {
query[kSecAttrAccount as String] = username
}
var returnDict = Dictionary<String, Data>()
// Find and cast the result as data.
var item: CFTypeRef?
switch SecItemCopyMatching(query as CFDictionary, &item) {
case errSecSuccess:
guard let dataDict = item as? Dictionary<String, Any> else { return nil }
if let data = dataDict[kSecAttrAccount as String] as? Data{
returnDict.updateValue(data, forKey: kSecAttrAccount as String)
}
if let data = dataDict[kSecAttrGeneric as String] as? Data{
returnDict.updateValue(data, forKey: kSecAttrGeneric as String)
}
returnDict.updateValue(dataDict[kSecValueData as String] as! Data, forKey: kSecValueData as String)
return returnDict
case errSecItemNotFound: return nil
case let status: throw TransnetError("Keychain read failed: \(status.message)")
}
}
This code drops into the errSecItemNotFound case.
Does anyone have any ideas what I'm doing wrong here?