SecItemCopyMatching, types and uniques

Hi,


I'm trying to fix an issue in RMStore and reviewing its keychain part. Based on best practise posted here and WWDC 2013 Session 709 "Protecting Secrets with the Keychain".


The issue in short: for a very small percentage of customers, the IAPs stored in the keychain are not accessible and restore of these IAPs also fails. No developer was able to reproduce the issue so far.


The following question will only focus on kSecClassGenericPassword and OS X.


When querying for an item it's best to query for just the keys that are required to make the item unique. For a generic password that's kSecClass, kSecAttrService and kSecAttrAccount. ( https://devforums.apple.com/message/959334#959334)


RMStore also stores and searches for kSecAttrGeneric, see here.


It seems to be no where document how important the right corresponding value per attribute is. A lot of sample code on the internet and the Apple GenericKeychain sample doesn't follow the documentation.


kSecAttrGeneric - Generic attribute key.
The corresponding value is of type CFDataRef and contains a user-defined attribute. Items of class kSecClassGenericPassword have this attribute.


kSecAttrAccount - Account attribute key.
The corresponding value is of type CFStringRef and contains an account name. Items of class kSecClassGenericPassword and kSecClassInternetPassword have this attribute.


Yet in GenericKeychain sample for kSecAttrGeneric a CFStringRef is used. And RMStore sets CFDataRef for kSecAttrAccount, see here.


Update: In the debugger if I query kSecReturnAttributes the attributes don't contain kSecAttrAccount. Only cdat, class, gena, labl, mdat, svce. So I assume because of the wrong type it doesn't get stored. Or is it because one of the two is choosen for uniques and the other is disregard?



Question 1: Is for kSecClassGenericPassword the attributes kSecClass, kSecAttrService, kSecAttrGeneric enough? Or does it require kSecAttrAccount?


Question 2: How is the query behaviour for attributes that are not required to make the item unique. Right now the code searchs for kSecClass, kSecAttrService, kSecAttrGeneric and kSecAttrAccount. See here. But in the store we only have kSecClass, kSecAttrService, kSecAttrGeneric. Yet the search works most of the times.


Recap of the issue: unable to access IAP and restore fails.


This suggest to me that the search for kSecClass, kSecAttrService, kSecAttrGeneric and kSecAttrAccount works most of the times. But can break for a currently unknown reason. The restore fails because it uses the same search and if nothing is found it trys do add the item. See here. But the add fails because an item with the unique part of kSecClass, kSecAttrService and kSecAttrGeneric already exists.


Would appreciate any insight into this!

Replies

To address your question about types, I’m going to have to start with some background. There are two implementations of the SecItem API:

  • the iOS implementation, which is also used for iCloud Keychain on OS X

  • the OS X implementation, which is a compatibility shim that bridges over to the traditional keychain

Note Both are available in Darwin. Search the Security project for

SecItemUpdate_ios
and
SecItemUpdate_osx
to see how this works under the covers.

The iOS implementation is based on SQLite. If you’re familiar with SQLite you’ll know that it is, at its core, untyped, and thus the iOS implementation has to do its own type conversion. This is why that implementation is somewhat forgiving on the types front. However…

IMPORTANT I strongly recommend that you use the types specified in the header. Other types do work but that’s an accident of the implementation rather than a designed in feature. Moreover, as there are two implementations, it’s not always the case that these accidents line up.

I realise that this rule is broken by various bits of Apple sample code. I’d be more than happy if you filed a bug about any cases where you notice that.

With regards the uniqueness constraints of various keychain items, the only definitive doc I know of for this is my post on the old DevForums. I took a quick look at the current state of affairs for

kSecClassGenericPassword
and it has not change substantially since then. this is officially documented in the reference docs for errSecDuplicateItem.

Uniqueness is only relevant for calls that update the database (

SecItemAdd
and
SecItemUpdate
). The database enforces that each update maintain the uniqueness constraints. For example, if the database contains the following generic passwords:
svce        acct
----        ----
door        gumby
door        pokey

here’s what you should expect:

  • add

    {"svce": "door", "acct": "gumby"}
    ?
    errSecDuplicateItem
  • add

    {"svce": "door", "acct": "minga"}
    ?
    errSecSuccess
  • add

    {"svce": "window", "acct": "gumby"}
    ?
    errSecSuccess
  • update

    {"svce": "door", "acct": "gumby"}
    to
    {"svce": "door", "acct": "pokey"}
    ?
    errSecDuplicateItem
  • update

    {"svce": "door", "acct": "gumby"}
    to
    {"svce": "door", "acct": "minga"}
    ?
    errSecSuccess
  • update

    {"svce": "door", "acct": "gumby"}
    to
    {"svce": "window", "acct": "gumby"}
    ?
    errSecSuccess

In contrast, uniqueness criteria are irrelevant to database queries. Database queries a very simple:

  • if you specify an attribute, you only get items where that attribute matches

  • if you leave out an attribute, you get all items

The most common cause of problems with this design is a mismatch between your query and the uniqueness criteria. For example, assuming the same database shown above, consider this sequence:

  1. copy

    {"svce": "door", "acct": "gumby", "gena": "foo"}
    ?
    errSecItemNotFound
  2. add

    {"svce": "door", "acct": "gumby", "gena": "foo"}
    ?
    errSecDuplicateItem

Argh!

However, if you understand the uniqueness criteria the reason for this is obvious: the query fails because you’ve included the

kSecAttrGeneric
attribute and that doesn’t match what’s in the database, while the add fails because that combination of
kSecAttrAccount
and
kSecAttrService
already exists in the database.

So, to come back to your specific questions:

Question 1: Is for kSecClassGenericPassword the attributes kSecClass, kSecAttrService, kSecAttrGeneric enough? Or does it require kSecAttrAccount?

That depends. The

kSecAttrAccount
attribute is not required, in that if you don’t supply it when you create the item, you effectively get an empty value. However, that empty value can cause confusion later on, so…

IMPORTANT I strongly recommend that you include values for all of the attributes included in the uniqueness constraint. That makes things simpler later on. If you don’t have a meaningful value, use a fixed value that’s not going to collide with anything (like a reverse DNS name).

Question 2: How is the query behaviour for attributes that are not required to make the item unique. Right now the code searchs for kSecClass, kSecAttrService, kSecAttrGeneric and kSecAttrAccount. See here. But in the store we only have kSecClass, kSecAttrService, kSecAttrGeneric. Yet the search works most of the times.

I believe I’ve addressed this point via the discussion above.

Share and Enjoy

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

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

Hi Quinn,


Thank you for your detailed answer.


A bug for the wrong type on GenericKeychain is filled under 26167065.


My test code shows that setting the wrong type on kSecAttrAccount results in the iCloud Keychain on OS X ignoring this attribute. I assume in the background it will create empty value like you suggest.


Running some test code, on a customer system which is effected with the issue, showed that the kSecAttrGeneric attribute was missing in the keychain item. Which explains why SecItemCopyMatching returns errSecItemNotFound.


So the question now is, how got this attribute missing? I don't see any code in RMStore that could be responsible for this. One idea is if a migration from traditional keychain to iCloud Keychain could be responible. Or the otherway around.


Cheers,

Christopher

So the question now is, how got this attribute missing?

I don’t have any good theories on that one, alas.

Share and Enjoy

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

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