jwvanderbeck
jwvanderbeck

Reputation: 67

SecItemUpdate() keeps returning errSecParam and I can't figure out why

Here is the entire function responsible for adding the value into the encrypted store. This is running on the iPhone6 Simulator in XCode 6.

func addOrUpdateGenericPassword(password: String, withService service: String, andAccount account: String, withAccessibility accessibility: SecurityAccessibility) -> ReturnStatus
{
    var sacObject: SecAccessControlRef
    var accessFlag: CFStringRef = kSecAttrAccessibleWhenUnlocked

    switch accessibility
    {
    case SecurityAccessibility.AccessibleAfterFirstUnlock:
        accessFlag = kSecAttrAccessibleAfterFirstUnlock
    case SecurityAccessibility.AccessibleAfterFirstUnlockThisDeviceOnly:
        accessFlag = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
    case SecurityAccessibility.AccessibleWhenUnlocked:
        accessFlag = kSecAttrAccessibleWhenUnlocked
    case SecurityAccessibility.AccessibleWhenUnlockedThisDeviceOnly:
        accessFlag = kSecAttrAccessibleWhenUnlockedThisDeviceOnly
    }

    if touchIDEnabled
    {
        sacObject = SecAccessControlCreateWithFlags(kCFAllocatorDefault, accessFlag, .USerPresence, nil).takeRetainedValue()
    }
    else
    {
        sacObject = SecAccessControlCreateWithFlags(kCFAllocatorDefault, accessFlag, nil, nil).takeRetainedValue()
    }

    var dataFromString: NSData = password.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
    var secKeys: [AnyObject] = [kSecClass, kSecAttrService, kSecAttrAccount, kSecValueData, kSecAttrAccessControl]
    var secValues: [AnyObject] = [kSecClassGenericPassword, service, account, dataFromString, sacObject]
    var update = NSDictionary(objects: secValues, forKeys: secKeys)

    // attempt to get the value first to determine if it exists
    let (currentValue, rStatus) = getGenericPasswordWith(service, andAccount: account)
    if rStatus == ReturnStatus.errSecSuccess
    {
        // Item already exists, so do an Update
        // We need to build a query for the existing value here instead of using the one we built for updating
        var query = buildQueryValueForLookupWith(service, andAccount: account)
        var status = SecItemUpdate(query, update)
        return getReturnStatusCodeForOSStatusCode(status)
    }
    else if rStatus == ReturnStatus.errSecItemNotFound
    {
        // Item does not exist so add it
        var status = SecItemAdd(update, nil)
        return getReturnStatusCodeForOSStatusCode(status)
    }
    else
    {
        // some other error occured and we dont know what is going on!
        println("Unknown errr occured when checking if item already exists.  Status = \(returnStatusCodeToString(rStatus))")
        return ReturnStatus.errSecUnknownError
    }
}
func buildQueryValueForLookupWith(service: String, andAccount account: String) -> NSDictionary
{
    var secKeys: NSArray = [kSecClass, kSecAttrService, kSecAttrAccount, kSecReturnData]
    var secValues: [AnyObject] = [kSecClassGenericPassword, service, account, true]
    var query = NSDictionary(objects: secValues, forKeys: secKeys)

    return query
}

That code is in the KeychainToolkit class I wrote. Here is an example of how I am calling it when I get the error:

var status = keychain.addOrUpdateGenericPassword(value!, withService: "healthBIT.lastSync", andAccount: key, withAccessibility: KeychainToolkit.SecurityAccessibility.AccessibleWhenUnlocked)

In this case the value is already in the keychain, and the above code properly detects that and uses SecItemUpdate() to just update its value. However it always returns errSecParam no matter what I do and I can't figure out where my params are wrong.

I have also tried it with completely removing the new iOS8 kSecAttrAccessControl part, with the same results.

Upvotes: 0

Views: 1675

Answers (1)

Carl Lindberg
Carl Lindberg

Reputation: 2972

I'm guessing you do not want kSecReturnData in the query dictionary for SecItemUpdate. There is no way to return the data to you with that API.

Upvotes: 1

Related Questions