4

我正在使用 Swift 玩一些 Mac OS X 钥匙串/安全 API。到目前为止,我已经能够从现有的钥匙串项中成功地获取用户名和密码(通过将kSecReturnAttributes和设置kSecReturnAttributestrue)。

这段代码由多个来源拼凑而成,包括 StackOverflow 和 Apple 开发论坛:

//
//  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

据我所知,由于 Keychain API 的性质,这段代码的丑陋基本上是不可避免的。当我在本地运行此代码时,我看到以下输出:

0
ACCOUNT: bgentry@fakedomain.com
PASSWORD: this.is.a.fake.password
Program ended with exit code: 0

话虽这么说,但有一件事特别难住了我。我从成功NSDictionaryAPIfoundDict调用SecItemAttrFourCharCode名为 例如,常量应该允许我从钥匙串项中检索用户帐户。这是它的 Objective-C 定义:kSecAccountItemAttr

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

在 Swift 中,我一直无法找到通过引用传递这些常量的任何方法,而是使用字符串来访问字典中的属性:

// 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

显然,我想避免在我的代码中为属性键溅出字符串,并且我还想避免在我的 Swift 代码中重新声明这些常量。

我怎样才能做到这一点?

4

1 回答 1

2

要设置或获取钥匙串项目的帐户名称,您必须使用kSecAttrAccount 作为密钥(它是一个NSString)而不是kSecAccountItemAttr。例子:

if let account = foundDict[kSecAttrAccount as NSString] as? NSString {
    println("ACCOUNT: \(account)")
}
于 2014-07-29T08:33:19.677 回答