iOS: Getting a persistent ref for a SecIdentityRef

I'm trying to get a persistent reference for a SecIdentityRef so I can reliably retrieve it later. I've tried a variety of things in the query parameter of SecItemCopyMatching, but I can't get it to work. I know the item exists in the keychain; I just barely called SecItemAdd not long ago. Here's what I've got right now:


let identityArray: CFArray = [identity] as CFArray
let query: [CFString: AnyObject] = [kSecClass: kSecClassIdentity,
                                    kSecMatchItemList: identityArray,
                                    kSecReturnPersistentRef: true]
   
var _persistentItems: AnyObject?
let copyResult = SecItemCopyMatching(query, &_persistentItems)
// copyResult is -50 (errSecParam)


I've tried the following other queries as well:


let query = [kSecMatchItemList: selfArray,
             kSecReturnPersistentRef: true]  // Produces -50 (errSecParam)


let query = [kSecClass: kSecClassIdentity,
             kSecValueRef: identity,
             kSecReturnPersistentRef: true] // Produces -25291 (errSecNotAvailable)


let query = [kSecValueRef: self,
             kSecReturnPersistentRef: true] // Produces -25300 (errSecItemNotFound)


Is it possible to get a persistent keychain ref to a SecIdentityRef on iOS? (For that matter, I need to do the same on OS X, but I've generally found that the OS X APIs are more likely to do what I expect.)

Replies

I've also considered the possibility that because a SecIdentity is a "virtual" object, it might not be possible to get a persistent ref to one. I could instead get a persistent reference to the SecCertificate, but I don't currently know of a way to go from a SecCertificate to a SecIdentity, and I'm relying on having an identity elsewhere.

In general I prefer to get the persistent ref to the identity as I import it. Here’s some code that does that:

func test() {
    let identity = importIdentity()

    var addResult: AnyObject?
    let addErr = SecItemAdd([
        kSecValueRef as String:            identity,
        kSecReturnPersistentRef as String:  true
    ], &addResult)
    assert(addErr == errSecSuccess);
    let persistentRef = addResult! as! NSData
    print(persistentRef)

    var copyResult: AnyObject?
    let copyErr = SecItemCopyMatching([
        kSecValuePersistentRef as String:  persistentRef,
        kSecReturnRef as String:            true
    ], &copyResult)
    assert(copyErr == errSecSuccess)
    let identity2 = copyResult! as! SecIdentity
    print(identity2)
}

If you have an object ref and you want to get a persistent ref, you can do that as well. For example, just add the following to the end of the code shown above.

var copyResult2: AnyObject?
let copyErr2 = SecItemCopyMatching([
    kSecValueRef as String:            identity2,
    kSecReturnPersistentRef as String:  true
], &copyResult2)
assert(copyErr2 == errSecSuccess)
let persistentRef2 = copyResult2! as! NSData
print(persistentRef2)

If you run this you’ll find that

persistentRef
and
persistentRef2
are the same value.

Share and Enjoy

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

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

Why is it not necessary to set this in SecItemAdd()?

kSecClass as String: kSecClassIdentity


The documentation of Storing an Identity in the Keychain is very misleading and it's implementation is actually not working (for me). I opened a Feedback/Bug Ticket: FB6706964

I agree with chrsgrf, documentation says

You use the kSecClass key with a suitable value to tell keychain services whether the data you want to store represents a password, a certificate, a cryptographic key, or something else.

But if you have that in your query dictionary, nothing is added at all! The call indicates success (returns 0) but it doesn't add anything to keychain. Only if you leave it out, something gets added -> Bug. I didn't know it was already reported, so I reported it as well FB9048257

Also Quinn said:

In general I prefer to get the persistent ref to the identity as I import it.

That is the only option that works at all on iOS. If you use kSecReturnAttributes, kSecReturnData, or kSecReturnRef, even when successfully added, the result is NULL. Only if you use kSecReturnPersistentRef it will not be NULL -> Bug. I filed the bug as FB9048313. This was already reported in 2017 for iOS 9 as radar 22228229 and closed as duplicate of 21810530

The keychain API is a horrible mess, especially on iOS. And its really sad that Apple completely lost interest in fixing any bugs reported by developers several year ago. I remember a time (around macOS 10.3/10.4) where we developer would report a bug, quite often even got feedback/questions there and with the next minor release it was fixed and the bug report was even correctly closed. Meanwhile reporting bugs is like talking to my cat. I have over 50 open bug reports on different accounts, all easily reproducible, some even critical and they are all entirely ignored for years. In some of them I even showed where in the source code the bug is and it's a one line change to fix it but it is not happening. And the biggest joke is, the Feedback interface itself is full of bugs and not even those get fixed.