Strange kSecReturnRef behavior for passwords on iOS

Hi,


I can't figure out what I am doing wrong. When querying the keychain on iOS, I expected that passing kSecReturnRef as part of a query would return the address of a SecKeychainItem on its own, as an array element or as part of a dictionary, depeding on the parameters, but queries via SecItemCopyMatching always seemed to return nil if only kSecReturnRef was specified.


I initially suspected that the search was, for some reason, not matching any keys, so I wrote some code to request a result when I was adding a newly created key with SecItemAdd. I saw the same thing. I altered the code to loop through all the combination of kSecReturn* and things got stranger still. First of all here's the code:


private func StoreKey() {
    for test in 0..<16 {
        let data       = test & 0x08 != 0
        let attributes = test & 0x04 != 0
        let ref        = test & 0x02 != 0
        let pref       = test & 0x01 != 0
        let addQuery = [
            kSecClass: kSecClassInternetPassword,
            kSecAttrLabel: UUID().uuidString,
            kSecAttrAccount: "Account-\(UUID().uuidString)",
            kSecValueData: String("SuperSecretPassword").data(using: .utf8)!,
            // Requested return info
            kSecReturnData: data,
            kSecReturnAttributes: attributes,
            kSecReturnPersistentRef: pref,
            kSecReturnRef: ref
        ] as NSDictionary
        
        print("=========== Query ==============")
        print("kSecReturnData         =\(addQuery[kSecReturnData]!)")
        print("kSecReturnAttributes   =\(addQuery[kSecReturnAttributes]!)")
        print("kSecReturnRef          =\(addQuery[kSecReturnRef]!)")
        print("kSecReturnPersistentRef=\(addQuery[kSecReturnPersistentRef]!)")
        print("=========== Result =============")
        
        var  result : CFTypeRef? = nil
        let  status = SecItemAdd(addQuery, &result)
        if status != noErr {
            print("Failed to store key \(status) \(SecCopyErrorMessageString(status, nil)!).")
        }
        else {
            printResult(result)
        }
        print("================================")
    }
}


The printResult function is just a pretty printer. For competeness here it is too.


private func printResult(_ result: CFTypeRef?){
    guard let result = result else {
        print("")
        return
    }
    switch result {
        case let dictionary as [String: Any]:
            print("{")
            for key in dictionary.keys.sorted() {
                let value = dictionary[key]!
                switch (key, value) {
                case ("agrp", _):
                    print("\t\(key) = \"\"")
                case (_, let v as String):
                    if !v.isEmpty { print("\t\(key) = \"\(value)\"") }
                case (_, let v as Int):
                    if v != 0 { print("\t\(key) = \(value)") }
                default: print("\t\(key) = \(value)")
                }
            }
            print("}")
        // Use NSData rather than Swift Data it provides more info
        case let data as NSData: return print(data)
        default: print("No special type \(result)")
    }
}


So if I set no flags I get nil.


=========== Query ==============
kSecReturnData         =0
kSecReturnAttributes   =0
kSecReturnRef          =0
kSecReturnPersistentRef=0
=========== Result =============

================================


OK, that's expected. If I ask for just a persistent ref that's what I get.


=========== Query ==============
kSecReturnData         =0
kSecReturnAttributes   =0
kSecReturnRef          =0
kSecReturnPersistentRef=1
=========== Result =============
{length = 12, bytes = 0x696e6574000000000000006f}
================================


So far, so good. But now ask for a transient ref...


=========== Query ==============
kSecReturnData         =0
kSecReturnAttributes   =0
kSecReturnRef          =1
kSecReturnPersistentRef=0
=========== Result =============

================================

Hmmm, that doesn't look correct, what if I ask for attributes and a transient ref...

=========== Query ==============
kSecReturnData         =0
kSecReturnAttributes   =1
kSecReturnRef          =1
kSecReturnPersistentRef=0
=========== Result =============
{
  acct = "Account-838E641A-A431-41FE-87AE-6FDA06D12FCD"
  agrp = ""
  cdat = 2019-06-14 08:37:28 +0000
  class = "inet"
  labl = "BFB77736-12F9-4A31-90B7-9CE66BEB0BA2"
  mdat = 2019-06-14 08:37:28 +0000
  pdmn = "ak"
  sha1 = {length = 20, bytes = 0x310e977f3aa5619fb5179ef96f217d0974ff4b85}
  v_Data = {length = 19, bytes = 0x537570657253656372657450617373776f7264} <<<<< WAT??????
}
================================

Still no transient ref, but WAT??? that's my super secret data, decrypted in the v_Data field. I did *not* ask for that.


Just as a sanity check, what happens if I ask for just the attibutes


=========== Query ==============
kSecReturnData         =0
kSecReturnAttributes   =1
kSecReturnRef          =0
kSecReturnPersistentRef=0
=========== Result =============
{
  acct = "Account-60B88887-06DF-440C-86EE-6FB9D146BD47"
  agrp = ""
  cdat = 2019-06-14 08:37:28 +0000
  labl = "0B437289-A31D-4841-81AC-2B22A07348CD"
  mdat = 2019-06-14 08:37:28 +0000
  pdmn = "ak"
  sha1 = {length = 20, bytes = 0x5e4a039e8ac20900158111f601992f28e84c5b18}
}
================================

OK, whew, no sensitive data there.


In summary, no combination of flags gave me the expected v_Ref dictionary entry or a SecKeychainItem, and when kSecReturnData was false, but kSecReturnRef was true and one or both of the other return flags was true the encrypted data was returned!!!


Can anybody explain what I am doing wrong?


This strange (or perhaps just misunderstood) behavior happens on iOS 13.0 beta and also iOS 12.3.1. Using Xcode 11 or 10 does not seem to change it. Objective-C also gives the same results.


Thanks in advance,

Danny

Post not yet marked as solved Up vote post of iOSDevZone Down vote post of iOSDevZone
870 views

Replies

I can’t explain all of your observations (I’d have to look at this in a lot more detail, which I don’t have time to do here on DevForums [1]) but the fundamental problem is that there’s no ref to return. If you set

kSecReturnRef
for a certificate query, you get back a
SecCertificate
object. If you set it for a key query, you get back a
SecKey
object. If you set it for an identity query, you get back a
SecIdentity
object. But what sort of object are you expecting to get back for an Internet password?

On macOS, assuming you’re targeting the old school file-based keychain, you could reasonably expect to get back a

SecKeychainItem
object. However, that type is not meaningful with iOS’s database-style keychain. You might also reasonably expect to get back a
SecKey
object with
kSecAttrKeyClassSymmetric
, but that’s similarly unsupported by iOS.

Share and Enjoy

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

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

[1] If you want me to dig into this in more detail, you should open a DTS tech support incident so that I can allocate more time.