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?

AstroCB
  • 12,337
  • 20
  • 57
  • 73
Damien
  • 660
  • 6
  • 15
  • 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? – Rob Jul 05 '14 at 14:05
  • Same issue with latest XCode as of 3/11/2015 – a432511 Mar 11 '15 at 15:45
  • If you're looking for a simple drop-in keychain wrapper, you could try this one: http://github.com/ashleymills/Keychain.swift – Ashley Mills Jun 18 '15 at 13:28

3 Answers3

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.

kishikawa katsumi
  • 10,418
  • 1
  • 41
  • 53
  • 1
    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). – Barthelemy Jan 24 '15 at 12:54
  • 1
    This should be the correct answer. The others won't work in a release build. – brettsam Feb 02 '15 at 14:51
  • 1
    You're a star, kishikawa. Unfortunately cannot use your wrapper as it requires 8.0, but that sorts me out. – Maciej Trybiło Feb 24 '15 at 16:21
  • 1
    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. – southp Mar 31 '15 at 08:55
  • +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. – a432511 Apr 06 '15 at 12:37
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.

Stuart Carnie
  • 5,458
  • 1
  • 29
  • 27
  • 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! – Damien Jun 16 '14 at 21:20
  • 7
    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) – JorgeDeCorte Nov 04 '14 at 10:44
  • 1
    I'm getting a bad access exception on the line `let opaque = dataTypeRef?.toOpaque()` (Xcode 6.1 & 6.2b) – Michael Waterfall Nov 20 '14 at 16:08
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