Using FourCharCode / SecItemAttr constants from Swift

1.2k Views Asked by At

I'm playing around with some Mac OS X Keychain / Security APIs using Swift. So far, I've been able to successfully fetch both a username and a password from an existing keychain item (by setting both kSecReturnAttributes and kSecReturnAttributes to true).

This code has been cobbled together from several sources including StackOverflow and the Apple Dev Forums:

//
//  main.swift
//  go-osxkeychain
//

import Foundation

// Create an HTTPS Keychain item w/ this server name before running this code
let serverName = "api.myserver.com"

public func getPassword(serverName: NSString) -> NSString? {
    // Instantiate a new default keychain query.
    // Tell the query to return the password data, as well as item attributes.
    // Limit our results to one item.
    var keychainQuery = NSMutableDictionary(
        objects: [
            kSecClassInternetPassword,
            serverName,
            kSecAttrProtocolHTTPS,
            true,
            true,
            kSecMatchLimitOne
        ], forKeys: [
            kSecClass,
            kSecAttrServer,
            kSecAttrProtocol,
            kSecReturnAttributes,
            kSecReturnData,
            kSecMatchLimit
        ])

    var dataTypeRef :Unmanaged<AnyObject>?

    // Search for the keychain items
    let status: OSStatus = SecItemCopyMatching(keychainQuery, &dataTypeRef)

    if Int(errSecSuccess) != Int(status) {
        println("error finding keychain item")
        return "ERROR"
    }

    let opaque = dataTypeRef?.toOpaque()

    if let op = opaque? {
        let retrievedDictData = Unmanaged<NSDictionary>.fromOpaque(op).takeUnretainedValue()
        let foundDict = NSDictionary(dictionary: retrievedDictData)

        // let account = foundDict[kSecAccountItemAttr as NSNumber] as String // BROKEN
        let account = foundDict["acct"] as String // WORKS
        println("ACCOUNT: \(account)")

        let foundSecret = NSString(data: foundDict[kSecValueData as NSString] as NSData, encoding: NSUTF8StringEncoding) as String
        println("PASSWORD: \(foundSecret)")

        return foundSecret
    } else {
        println("Nothing was retrieved from the keychain. Status code \(status)")
    }

    return ""
}

let contents = getPassword(serverName) as String

As far as I know, the ugliness of this code is basically unavoidable due to the nature of the Keychain APIs. When I run this code locally, I see the following output:

0
ACCOUNT: [email protected]
PASSWORD: this.is.a.fake.password
Program ended with exit code: 0

That being said, one thing in particular has stumped me. The NSDictionary of keychain item attributes I get back from a successful API call (named foundDict) contains keys that correspond to SecItemAttr / FourCharCode constants, shown here. For example, the constant kSecAccountItemAttr should allow me to retrieve the user account from the keychain item. Here's its Objective-C definition:

typedef FourCharCode SecItemAttr;
enum
{
    ...
    kSecAccountItemAttr             = 'acct',
    ...
}

In Swift, I've been unable to find any way to pass in these constants by reference, instead using strings to access attributes from the dictionary:

// Runtime error, "fatal error: unexpectedly found nil while unwrapping an Optional value"
let account = foundDict[kSecAccountItemAttr] as String

// Runtime error, "fatal error: unexpectedly found nil while unwrapping an Optional value"
let account = foundDict[kSecAccountItemAttr as NSNumber] as String

// Compiler error, "type 'FourCharCode' does not conform to protocol 'NSCopying'"
let account = foundDict[kSecAccountItemAttr as FourCharCode] as String

// Compiler error, "'Int' is not convertible to 'String'"
let account = foundDict[kSecAccountItemAttr as String] as String

// This works:
let account = foundDict["acct"] as String

Obviously I'd like to avoid splattering strings all over my code for my attribute keys, and I'd also like to avoid having to redeclare these constants in my Swift code.

How can I do that?

1

There are 1 best solutions below

3
On BEST ANSWER

To set or get the account name of the keychain item, you have to use kSecAttrAccount as the key (which is an NSString) and not kSecAccountItemAttr. Example:

if let account = foundDict[kSecAttrAccount as NSString] as? NSString {
    println("ACCOUNT: \(account)")
}