update keychain item and get errSecDuplicateItem

Hi there,


I have a problem when I trying to update the password/email in the keychain.


I check for if the keychain item exists first

and then if exist I update the item.

Then the status code giving back is -25299 which is errSecDuplicateItem.

Why would that happen?

Am I doing this correctlly?




OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)keychainItem, NULL)
if(status == errSecSuccess) {
     //keychain item already exist I need to update the item
     status = SecItemUpdate((__bridge CFDictionaryRef)newKeychainItem, (__bridge CFDictionaryRef)updateAttributes);
     if(status == errSecSuccess) {
          return YES;
     } else {
          if(error != nil) {
               *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
          }
          return NO;
     }
}else{
     //keychain item does not exist I need to add item to the keychain
}

Accepted Reply

The doc comments for

SecItemDelete
say:

To delete an item identified by a persistent reference, on iOS, specify

kSecValuePersistentRef
with a persistent reference returned by …

and:

on OSX, use

kSecMatchItemList
with a persistent reference returned by …

The code you posted seems to be using the macOS approach, but you said you’re running on iOS.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Replies

The documentation for

errSecDuplicateItem
explains the attributes that must be unique for any given keychain item class. Most folks who have this problem because they’re confused about the semantics of the dictionaries used by the various keychain APIs (and, I’ll admit, that is quite confusing). I’ve posted about this extensively in the past, for example, this post.

In your case you are using two different dictionaries,

keychainItem
and
newKeychainItem
, and it’s hard to say what’s going on here without knowing more about the content of those dictionaries and the state of your keychain.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks for helping, I found what happend, it turns out an outside library is using our app bundle identifier created a new keychain.

So every time I do a search it serched for the wrong thing.


Right now I am doing a migration on the keychainManager, I am trying looping through the keychain items and delete the old one I added.

Here is my code


  NSMutableDictionary *searchKeychainItem = [NSMutableDictionary dictionary];
  searchKeychainItem[(__bridge id)kSecClass] = (__bridge id)kSecClassGenericPassword;
  searchKeychainItem[(__bridge id)kSecAttrAccessible] = (__bridge id)kSecAttrAccessibleAfterFirstUnlock;
  searchKeychainItem[(__bridge id)kSecAttrService] = BlinkServiceKey;
  searchKeychainItem[(__bridge id)kSecReturnData] = (__bridge id)kCFBooleanTrue;
  searchKeychainItem[(__bridge id)kSecReturnAttributes] = (__bridge id)kCFBooleanTrue;
  searchKeychainItem[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitAll;
  searchKeychainItem[(__bridge id)kSecReturnPersistentRef] = (__bridge id)kCFBooleanTrue;

  CFArrayRef result = nil;
  OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)searchKeychainItem, (CFTypeRef *)&result);
  if (status == errSecSuccess && result != nil){
    NSArray *resultArray = (__bridge_transfer NSArray *)result;
    for (NSDictionary *keychainItem in resultArray){
      NSString *userEmail = keychainItem[(__bridge id)kSecAttrAccount];
     //check for if the account is a email address format
     if(IsEmailAddressValidLooking(userEmail)){
        //delete old email/password keychain item
        NSDictionary *deleteItem = [NSDictionary dictionaryWithObjectsAndKeys:
                                    (__bridge id)(kSecClassIdentity), kSecClass,
                                    [NSArray arrayWithObject:keychainItem], kSecMatchItemList,
                                    kSecMatchLimitOne, kSecMatchLimit,
                                    nil];
        OSStatus deleteStatus = SecItemDelete((__bridge CFDictionaryRef)deleteItem);
        if(deleteStatus != errSecSuccess){
          //failed here with error code -50
          @throw [NSException exceptionWithName:BlinkKeychainManagerException reason:[NSString stringWithFormat:@"Failed to delete old email/password keychain item with error code %d", deleteStatus] userInfo:nil];
        }
        break;
      }
    }
  }


It failed on the delete, here is the apple document link for this https://developer.apple.com/documentation/security/ksecmatchitemlist?language=objc

"

To delete an item identified by a persistent reference, specify the

kSecMatchItemList
search key in a call to the
SecItemDelete
function with a persistent reference returned by using the
kSecReturnPersistentRef
return type key to the
SecItemCopyMatching
or
SecItemAdd
functions.

"

How do I use persistent reference to delete a keychain item that returned by SecItemCopyMatching?

Am I doing it wrong?

How do I use persistent reference to delete a keychain item that returned by

SecItemCopyMatching
?

What platform are you on? If it’s macOS, are you targeting the older file-based keychain or the new iOS-style keychain?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I am on iOS


Just be clear that I know I can create a new dictionary that has the same account and service key, then call a delete.

I am not sure this is the correct way to do it

Thats why I am asking how to delete the keychain item comes from the result(returned by SecItemCopyMatching).

The doc comments for

SecItemDelete
say:

To delete an item identified by a persistent reference, on iOS, specify

kSecValuePersistentRef
with a persistent reference returned by …

and:

on OSX, use

kSecMatchItemList
with a persistent reference returned by …

The code you posted seems to be using the macOS approach, but you said you’re running on iOS.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thank you, that fixed the problem.


May I also ask that if my code is correct and it works.

Is there something else could cause the keychain save and delete operation fail? something like storge full or keychain corrupted for some reason.

Is there something else could cause the keychain save and delete operation fail?

I’m not sure I understand the motivation for this question. There are, indeed, many reasons that the keychain might return an error. If your code is correct then these errors are unusual — they are either the result of bugs in the OS or extreme environmental conditions — but they can still happen. I recommend that you write keychain code to do something sensible with these errors. Specifically, make sure you have a way to get info about keychain errors from users who experience problems (that could be something as simple as logged errors to

<os/log.h>
.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks, that helps a lot.