Querying iOS Keychain using Swift
Asked Answered
U

3

17

I'm stuck converting the Keychain query result using Swift.

My request seems to be working:

let queryAttributes = NSDictionary(objects: [kSecClassGenericPassword, "MyService",     "MyAccount",       true],
                                   forKeys: [kSecClass,                kSecAttrService, kSecAttrAccount, kSecReturnData])


dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
    var dataTypeRef : Unmanaged<AnyObject>?
    let status = SecItemCopyMatching(queryAttributes, &dataTypeRef);

    let retrievedData : NSData = dataTypeRef!.takeRetainedValue() as NSData
    *** ^^^^can't compile this line^^^^
})

My problem is, code won't compile:

Bitcast requires both operands to be pointer or neither
  %114 = bitcast %objc_object* %113 to %PSs9AnyObject_, !dbg !487
Bitcast requires both operands to be pointer or neither
  %115 = bitcast %PSs9AnyObject_ %114 to i8*, !dbg !487
LLVM ERROR: Broken function found, compilation aborted!

I don't know how to convert Unmanaged<AnyObject> to NSData.

Any ideas?

Uplift answered 10/6, 2014 at 16:8 Comment(3)
I am also trying to get access to the iOS Keychain and I saw your post. I can't figure out how to get the query dictionary created. I even copied your first line above into my application and it says the same thing. "Could not find an overload for 'init' that accepts the supplied arguments". Have I missed something?Definiendum
Same issue with latest XCode as of 3/11/2015Fardel
If you're looking for a simple drop-in keychain wrapper, you could try this one: github.com/ashleymills/Keychain.swiftChyou
D
18

Use withUnsafeMutablePointer function and UnsafeMutablePointer struct to retrieving the data, such as the following:

var result: AnyObject?
var status = withUnsafeMutablePointer(&result) { SecItemCopyMatching(queryAttributes, UnsafeMutablePointer($0)) }

if status == errSecSuccess {
    if let data = result as NSData? {
        if let string = NSString(data: data, encoding: NSUTF8StringEncoding) {
            // ...
        }
    }
}

it works fine with release (Fastest [-O]) build.

Deneendenegation answered 31/12, 2014 at 12:42 Comment(5)
This was the only solution that worked for me when releasing the application (other solutions worked well in xcode, but not when distributing the app in a beta release).Aubry
This should be the correct answer. The others won't work in a release build.Pentathlon
You're a star, kishikawa. Unfortunately cannot use your wrapper as it requires 8.0, but that sorts me out.Nolte
In my case, this was also the only workable solution in a release build. Also, I'd love to know why other implementation won't work in release build.Anathematize
+1 This worked for me too. Thank you so much! The other method only worked on iPhone 5S+. On the iPhone 5- it would crash.Fardel
A
10

Looks like you have hit a compiler bug, which you should report. You can take a different path to retrieving the value, such as the following:

    var dataTypeRef :Unmanaged<AnyObject>?
    let status = SecItemCopyMatching(queryAttributes, &dataTypeRef);

    let opaque = dataTypeRef?.toOpaque()

    if let op = opaque? {
        let retrievedData = Unmanaged<NSData>.fromOpaque(op).takeUnretainedValue()

    }

The error manifests itself when using AnyObject as a type parameter T of Unmanaged<T>. The following code snippet compiles without issue, which uses a more specific type, CFError:

    let url = NSURL(string:"dummy")
    var errorRef: Unmanaged<CFError>?
    let succeeded = CTFontManagerRegisterFontsForURL(url, .Process, &errorRef)

    if errorRef {
        let error = errorRef!.takeRetainedValue()
    }

As the Keychain API returns a different result depending of the query attributes, the use of AnyObject is required.

Articular answered 13/6, 2014 at 4:54 Comment(3)
Thanks so much for the answer. I'll report it then and maybe wait to see if fixed in next beta. If not, I'll use this code. Thanks again!Uplift
It looks like this is working when you set the Swift Compiler - Optimization Level to -Onone. But when you set it to -O. Opaque will be nil. Build with Xcode 6.1(6A1052d)Chairman
I'm getting a bad access exception on the line let opaque = dataTypeRef?.toOpaque() (Xcode 6.1 & 6.2b)Unfeeling
M
0

To get this to work you need to access the retained value for each keychain constant first. For example:

let kSecClassValue = kSecClass.takeRetainedValue() as NSString
let kSecAttrAccountValue = kSecAttrAccount.takeRetainedValue() as NSString
let kSecValueDataValue = kSecValueData.takeRetainedValue() as NSString
let kSecClassGenericPasswordValue = kSecClassGenericPassword.takeRetainedValue() as NSString
let kSecAttrServiceValue = kSecAttrService.takeRetainedValue() as NSString
let kSecMatchLimitValue = kSecMatchLimit.takeRetainedValue() as NSString
let kSecReturnDataValue = kSecReturnData.takeRetainedValue() as NSString
let kSecMatchLimitOneValue = kSecMatchLimitOne.takeRetainedValue() as NSString 

You'll then need to reference the constant you created in the keychain dictionary object like so.

var keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, userAccount, kCFBooleanTrue, kSecMatchLimitOneValue], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecReturnDataValue, kSecMatchLimitValue])

I wrote a blog post about it at: http://rshelby.com/2014/08/using-swift-to-save-and-query-ios-keychain-in-xcode-beta-4/

Hope this helps!

rshelby

Mime answered 10/8, 2014 at 14:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.