Добавление элемента в цепочку ключей с помощью Swift

Я пытаюсь добавить элемент в keychain iOS с помощью Swift, но не могу понять, как правильно набирать литеры. Из сессии 709 WWDC 2013, учитывая следующий код Objective-C:

NSData *secret = [@"top secret" dataWithEncoding:NSUTF8StringEncoding];
NSDictionary *query = @{
    (id)kSecClass: (id)kSecClassGenericPassword,
    (id)kSecAttrService: @"myservice",
    (id)kSecAttrAccount: @"account name here",
    (id)kSecValueData: secret,
};

OSStatus = SecItemAdd((CFDictionaryRef)query, NULL);

Попытка сделать это в Swift следующим образом:

var secret: NSData = "Top Secret".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
var query: NSDictionary = [
    kSecClass: kSecClassGenericPassword,
    kSecAttrService: "MyService",
    kSecAttrAccount: "Some account",
    kSecValueData: secret
]

дает ошибку "Невозможно преобразовать тип выражения" Словарь "в" СловарьLiteralConvertible ".

Другим подходом, который я взял, было использование Swift и метода - setObject:forKey: в словаре для добавления kSecClassGenericPassword с ключом kSecClass.

В Objective-C:

NSMutableDictionary *searchDictionary = [NSMutableDictionary dictionary];
[searchDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];

В коде Objective-C CFTypeRef различных ключей класса элемента keychain соединяется с использованием id. В Swift documentation он упомянул, что Swift импортирует id как AnyObject. Однако, когда я попытался понизить kSecClass как AnyObject для метода, я получаю сообщение об ошибке "Тип" AnyObject "не соответствует NSCopying.

Любая помощь, будь то прямой ответ или какое-либо руководство о том, как взаимодействовать с типами Core Foundation, будет оценено.

РЕДАКТИРОВАТЬ 2

Это решение больше недействительно с Xcode 6 Beta 2. Если вы используете бета-версию 1, приведенный ниже код может работать.

var secret: NSData = "Top Secret".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
let query = NSDictionary(objects: [kSecClassGenericPassword, "MyService", "Some account", secret], forKeys: [kSecClass,kSecAttrService, kSecAttrAccount, kSecValueData])

OSStatus status = SecItemAdd(query as CFDictionaryRef, NULL)

Чтобы использовать ключи атрибута Keychain Item в качестве ключей словаря, вам необходимо развернуть их, используя либо takeRetainedValue, либо takeUnretainedValue (при необходимости). Затем вы можете отправить их в NSCopying. Это потому, что они являются CFTypeRefs в заголовке, которые не все могут быть скопированы.

Как и в случае с Xcode 6 Beta 2, это приводит к сбою Xcode.

Ответ 1

В xcode 6.0.1 вы должны это сделать!

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

Ответ 2

Вам просто нужно опустить букву:

let dict = ["hi": "Pasan"] as NSDictionary

Теперь dict является NSDictionary. Чтобы сделать изменчивым, он очень похож на Objective-C:

let mDict = dict.mutableCopy() as NSMutableDictionary
mDict["hola"] = "Ben"

Ответ 3

Возможно, с тех пор ситуация улучшилась. На Xcode 7 beta 4 никакое кастинг не требуется, кроме случаев, когда имеет дело с результатом AnyObject?. В частности, работает следующее:

var query : [NSString : AnyObject] = [
    kSecClass : kSecClassGenericPassword,
    kSecAttrService : "MyAwesomeService",
    kSecReturnAttributes : true,   // return dictionary in result parameter
    kSecReturnData : true          // include the password value
]
var result : AnyObject?
let err = SecItemCopyMatching(query, &result)
if (err == errSecSuccess) {
    // on success cast the result to a dictionary and extract the
    // username and password from the dictionary.
    if let result = result as ? [NSString : AnyObject],
       let username = result[kSecAttrAccount] as? String,
       let passdata = result[kSecValueData] as? NSData,
       let password = NSString(data:passdata, encoding:NSUTF8StringEncoding) as? String {
        return (username, password)
    }
} else if (status == errSecItemNotFound) {
    return nil;
} else {
    // probably a program error,
    // print and lookup err code (e.g., -50 = bad parameter)
}

Чтобы добавить ключ, если он отсутствует:

var query : [NSString : AnyObject] = [
    kSecClass : kSecClassGenericPassword,
    kSecAttrService : "MyAwesomeService",
    kSecAttrLabel : "MyAwesomeService Password",
    kSecAttrAccount : username,
    kSecValueData : password.dataUsingEncoding(NSUTF8StringEncoding)!
]
let result = SecItemAdd(query, nil)
// check that result is errSecSuccess, etc...

Несколько замечаний: ваша первоначальная проблема, возможно, заключалась в том, что str.dataUsingEncoding возвращает Необязательный. Добавление '!' или еще лучше, используя if let для обработки нулевого возврата, скорее всего, сделает ваш код работать. Распечатка кода ошибки и поиск его в документах поможет много в изоляции проблемы (я получал err -50 = плохой параметр, пока не заметил проблему с моим kSecClass, ничего общего с типами данных или отбрасыванием!).

Ответ 4

Казалось, что это нормально, или, по крайней мере, у компилятора не было котят - UNTESTED за пределами этого

        var secret: NSData = "Top Secret".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
        var array1 = NSArray(objects:"\(kSecClassGenericPassword)", "MyService", "Some account", secret)
        var array2 = NSArray(objects:"\(kSecClass)","\(kSecAttrService)", "\(kSecAttrAccount)","\(kSecValueData)")
        let query = NSDictionary(objects: array1, forKeys: array2)
        println(query)
        let status  = SecItemAdd(query as CFDictionaryRef, nil)

Кажется, хорошо работает в бета-версии 2

Ответ 5

Чтобы заставить это работать, вам нужно получить доступ к сохраненным значениям констант связки ключей. Например:

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

Затем вы можете ссылаться на значения в MSMutableDictionary следующим образом:

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

Я написал сообщение в блоге об этом: http://rshelby.com/2014/08/using-swift-to-save-and-query-ios-keychain-in-xcode-beta-4/

Надеюсь, это поможет!

rshelby

Ответ 6

Swift 3

let kSecClassKey = String(kSecClass)
let kSecAttrAccountKey = String(kSecAttrAccount)
let kSecValueDataKey = String(kSecValueData)
let kSecClassGenericPasswordKey = String(kSecClassGenericPassword)
let kSecAttrServiceKey = String(kSecAttrService)
let kSecMatchLimitKey = String(kSecMatchLimit)
let kSecReturnDataKey = String(kSecReturnData)
let kSecMatchLimitOneKey = String(kSecMatchLimitOne)

вы также можете сделать это внутри самого словаря alá:

var query: [String: Any] = [
    String(kSecClass): kSecClassGenericPassword,
    String(kSecAttrService): "MyService",
    String(kSecAttrAccount): "Some account",
    String(kSecValueData): secret
]

однако, это более дорого для компилятора, тем более, что вы, вероятно, используете запрос в нескольких местах.

Ответ 7

более удобно использовать cocoa pods SSKeychain

+ (NSArray *)allAccounts;
+ (NSArray *)accountsForService:(NSString *)serviceName;
+ (NSString *)passwordForService:(NSString *)serviceName account:(NSString *)account;
+ (BOOL)deletePasswordForService:(NSString *)serviceName account:(NSString *)account;
+ (BOOL)setPassword:(NSString *)password forService:(NSString *)serviceName account:(NSString *)account;