Trying to use KeychainItemWrapper by Apple "translated" to Swift
Asked Answered
C

4

8

Sigh, I have been working on this the whole afternoon... here is my nightmare:

I am trying to use the KeychainItemWrapper made by Apple. But I "translated" its Objective-C codes to Swift:

import Foundation
import Security
class MyKeychainItemWrapper: NSObject {
var keychainItemData: NSMutableDictionary?
var genericPasswordQuery: NSMutableDictionary = NSMutableDictionary()

init(identifier: String, accessGroup: String?) {

    super.init()

    // Begin Keychain search setup. The genericPasswordQuery leverages the special user
    // defined attribute kSecAttrGeneric to distinguish itself between other generic Keychain
    // items which may be included by the same application.
    genericPasswordQuery.setObject(kSecClassGenericPassword, forKey: kSecClass)
    genericPasswordQuery.setObject(identifier, forKey: kSecAttrGeneric)

    // The keychain access group attribute determines if this item can be shared
    // amongst multiple apps whose code signing entitlements contain the same keychain access group.
    println(accessGroup)
    if (!(accessGroup == nil)) {
        genericPasswordQuery.setObject(accessGroup!, forKey: kSecAttrAccessGroup)
    }

    // Use the proper search constants, return only the attributes of the first match.
    genericPasswordQuery.setObject(kSecMatchLimitOne, forKey: kSecMatchLimit)
    genericPasswordQuery.setObject(kCFBooleanTrue, forKey: kSecReturnAttributes)

    var tempQuery: NSDictionary = NSDictionary(dictionary: genericPasswordQuery)

    var outDictionary: Unmanaged<AnyObject>? = nil

    var status: OSStatus = SecItemCopyMatching(tempQuery as CFDictionaryRef, &outDictionary)
    println(status == noErr)

    if (status == noErr) {
        // Stick these default values into keychain item if nothing found.
        resetKeychainItem()

        // Add the generic attribute and the keychain access group.
        keychainItemData!.setObject(identifier, forKey: kSecAttrGeneric)

        if (!(accessGroup == nil)) {
            keychainItemData!.setObject(accessGroup!, forKey: kSecAttrAccessGroup)
        }
    } else {
        // load the saved data from Keychain.
        keychainItemData = secItemFormatToDictionary(outDictionary?.takeRetainedValue() as NSDictionary)
    }
}

Then in my app's AppDelegate.swift, I am trying to use it by:

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    var passwordItem: MyKeychainItemWrapper = MyKeychainItemWrapper(identifier: "Password", accessGroup: nil)
...

So, the initializer is called, but but but somehow, I ALWAYS, ALWAYS get

Thread 1: EXC_BREAKPOINT (code=EXC_ARM_BREAKPOINT, subcode=0xe7ffdefe)

enter image description here

I have tried commenting out the problem lines and then I get this error at another if(): enter image description here

I even tried:

var mmm: Bool = (accessGroup == nil)
if (!mmm) {
  genericPasswordQuery.setObject(accessGroup!, forKey: kSecAttrAccessGroup)
}

But same error at the same place, i.e. if(..)

I am now so confused. Did I miss something here or?

Environment: Xcode6-beta6, iOS 8 beta 5 on a non-jailbroken iPhone 5.

Claud answered 26/8, 2014 at 18:48 Comment(0)
B
19

Swift 3

import UIKit
import Security

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

struct KeychainAccess {

    func setPasscode(identifier: String, passcode: String) {
        if let dataFromString = passcode.data(using: String.Encoding.utf8) {
            let keychainQuery = [
                kSecClassValue: kSecClassGenericPasswordValue,
                kSecAttrServiceValue: identifier,
                kSecValueDataValue: dataFromString
            ] as CFDictionary
            SecItemDelete(keychainQuery)
            print(SecItemAdd(keychainQuery, nil))
        }
    }

    func getPasscode(identifier: String) -> String? {
        let keychainQuery = [
            kSecClassValue: kSecClassGenericPasswordValue,
            kSecAttrServiceValue: identifier,
            kSecReturnDataValue: kCFBooleanTrue,
            kSecMatchLimitValue: kSecMatchLimitOneValue
        ] as  CFDictionary
        var dataTypeRef: AnyObject?
        let status: OSStatus = SecItemCopyMatching(keychainQuery, &dataTypeRef)
        var passcode: String?
        if (status == errSecSuccess) {
            if let retrievedData = dataTypeRef as? Data,
                let result = String(data: retrievedData, encoding: String.Encoding.utf8) {
                passcode = result as String
            }
        }
        else {
            print("Nothing was retrieved from the keychain. Status code \(status)")
        }
        return passcode
    }
}

Swift 2

import UIKit;
import Security;


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


class KeychainAccess: NSObject {

func setPasscode(identifier: String, passcode: String) {
    let dataFromString: NSData = passcode.dataUsingEncoding(NSUTF8StringEncoding)!;
    let keychainQuery = NSDictionary(
    objects: [kSecClassGenericPasswordValue, identifier, dataFromString],
    forKeys: [kSecClassValue, kSecAttrServiceValue, kSecValueDataValue]);
    SecItemDelete(keychainQuery as CFDictionaryRef);
    let status: OSStatus = SecItemAdd(keychainQuery as CFDictionaryRef, nil);
}


func getPasscode(identifier: String) -> NSString? {
    let keychainQuery = NSDictionary(
    objects: [kSecClassGenericPasswordValue, identifier, kCFBooleanTrue, kSecMatchLimitOneValue],
    forKeys: [kSecClassValue, kSecAttrServiceValue, kSecReturnDataValue, kSecMatchLimitValue]);
    var dataTypeRef: AnyObject?
    let status: OSStatus = SecItemCopyMatching(keychainQuery, &dataTypeRef)
    var passcode: NSString?;
    if (status == errSecSuccess) {
        let retrievedData: NSData? = dataTypeRef as? NSData
        if let result = NSString(data: retrievedData!, encoding: NSUTF8StringEncoding) {
            passcode = result as String
        }
    }
    else {
        print("Nothing was retrieved from the keychain. Status code \(status)")
    }
    return passcode;
   }
}

Then from anywhere simply call:

func setPasscode(passcode: String) {
    let keychainAccess = KeychainAccess();
    keychainAccess.setPasscode("YourAppIdentifier", passcode:passcode);
}


func getPasscode() -> NSString {
    let keychainAccess = KeychainAccess();
    return keychainAccess.getPasscode("YourAppIdentifier")!;
}


func deletePasscode() {
    let keychainAccess = KeychainAccess();
    keychainAccess.setPasscode("YourAppIdentifier", passcode:"");
}
Boyette answered 21/9, 2014 at 10:58 Comment(6)
This looks really good and I'll probably use the bulk of this example. Is there a reason that you wrapped the two functions as instance methods on the class rather left them than as pure top level functions or as class methods?Pomeranian
Also the keychainQuery variables seem to need to be NSDictionary types (I'm using Xcode 6.1.1) - NSMutableDictionary is the specific one that worked for me but non-mutable may also work.Pomeranian
When I copy and paste that code into a class file I get errors. For example the line: var keychainQuery = [ Gives me the error: "NSString is not a subtype of NSData"Vevay
@Boyette For kSecClass I get an error: Value of type CFString does not conform to expected element type 'NSCopying'. This is for Swift 2.0. Any idea how to fix this ?Kaitlinkaitlyn
That works - thanks for the quick update! Why do you use let kSecClassValue = NSString(format: kSecClass); instead of let kSecClassValue = (format: kSecClass as String); ?Kaitlinkaitlyn
@karlml - no specific reason, I was re-translating this several times since objective c times... probably it is better to change now all NSString constants to String, give it try ..Boyette
N
5

official is GenericKeychain

existing several swift version, the best one is:

jrendel/SwiftKeychainWrapper · GitHub

how to use it:

  1. download file: KeychainWrapper.swift
  2. write code to set/get/delete:

    let StrUsernameKey:String = "username"
    let StrPasswordKey:String = "password"
    
    let saveSuccessful: Bool = KeychainWrapper.setString(usernameTextField.text!, forKey: StrUsernameKey)
    print("saveSuccessful=\(saveSuccessful)") //saveSuccessful=true
    let retrievedString: String? = KeychainWrapper.stringForKey(StrUsernameKey)
    print("retrievedString=\(retrievedString)") //retrievedString=Optional("yourLastStoredUsernameString")
    let removeSuccessful: Bool = KeychainWrapper.removeObjectForKey(StrUsernameKey)
    print("removeSuccessful=\(removeSuccessful)") //removeSuccessful=true
    let retrievedStringAfterDelete: String? = KeychainWrapper.stringForKey(StrUsernameKey)
    print("retrievedStringAfterDelete=\(retrievedStringAfterDelete)") //retrievedStringAfterDelete=nil
    
Nesselrode answered 18/11, 2015 at 7:36 Comment(0)
T
4

Updates for Swift 2.

Here is an example implementation that may help.:

import Security

class ZLKeychainService: NSObject {

    var service = "Service"
    var keychainQuery :[NSString: AnyObject]! = nil

    func save(name name: NSString, value: NSString) -> OSStatus? {
        let statusAdd :OSStatus?

        guard let dataFromString: NSData = value.dataUsingEncoding(NSUTF8StringEncoding) else {
            return nil
        }

        keychainQuery = [
            kSecClass       : kSecClassGenericPassword,
            kSecAttrService : service,
            kSecAttrAccount : name,
            kSecValueData   : dataFromString]
        if keychainQuery == nil {
            return nil
        }

        SecItemDelete(keychainQuery as CFDictionaryRef)

        statusAdd = SecItemAdd(keychainQuery! as CFDictionaryRef, nil)

        return statusAdd;
    }

    func load(name name: NSString) -> String? {
        var contentsOfKeychain :String?

        keychainQuery = [
            kSecClass       : kSecClassGenericPassword,
            kSecAttrService : service,
            kSecAttrAccount : name,
            kSecReturnData  : kCFBooleanTrue,
            kSecMatchLimit  : kSecMatchLimitOne]
        if keychainQuery == nil {
            return nil
        }

        var dataTypeRef: AnyObject?
        let status: OSStatus = SecItemCopyMatching(keychainQuery, &dataTypeRef)

        if (status == errSecSuccess) {
            let retrievedData: NSData? = dataTypeRef as? NSData
            if let result = NSString(data: retrievedData!, encoding: NSUTF8StringEncoding) {
                contentsOfKeychain = result as String
            }
        }
        else {
            print("Nothing was retrieved from the keychain. Status code \(status)")
        }

        return contentsOfKeychain
    }
}

//Test:
let userName = "TestUser"
let userValue: NSString = "TestValue"
print("userName: '\(userName)'")
print("userValue: '\(userValue)'")

let kcs = ZLKeychainService()

kcs.save(name:userName, value: userValue)
print("Keychain Query \(kcs.keychainQuery)")

if let recoveredToken = kcs.load(name:userName) {
    print("Recovered Value: '\(recoveredToken)'")
}

Output:

userName: 'TestUser'
userValue: 'TestValue'
Keychain Query [acct: TestUser, v_Data: <54657374 56616c75 65>, svce: Service, class: genp]
Recovered Value: 'TestValue'

Tracery answered 26/8, 2014 at 19:1 Comment(3)
Thanks for the help. I figured out a workaround that works for now. But this problem may still remains.Claud
@HillInHarwich why does this solution not work for you?Tracery
@ileonard It did not work for me - but the reason (as I found out typically soon after posting to here) was that I was using another project - and although I had added the framework/project to the normal list, I had not added put the frame work in the "Include in binary" list - the proper name of which escapes me now.Botulinus
C
0

My solution seems working:

init(identifier: String) {

    super.init()

    genericPasswordQuery.setObject(kSecClassGenericPassword, forKey: kSecClass as String)
    genericPasswordQuery.setObject(identifier, forKey: kSecAttrGeneric as String)

    // Use the proper search constants, return only the attributes of the first match.
    genericPasswordQuery.setObject(kSecMatchLimitOne, forKey: kSecMatchLimit as String)
    genericPasswordQuery.setObject(kCFBooleanTrue, forKey: kSecReturnAttributes as String)

    var tempQuery: NSDictionary = NSDictionary(dictionary: genericPasswordQuery)

    var outDictionary: Unmanaged<AnyObject>? = nil

    let status: OSStatus = SecItemCopyMatching(tempQuery as CFDictionaryRef, &outDictionary)
    var result: NSDictionary? = outDictionary?.takeRetainedValue() as NSDictionary?
    if (result == nil) {
        // Stick these default values into keychain item if nothing found.
        resetKeychainItem()

        // Add the generic attribute and the keychain access group.
        keychainItemData!.setObject(identifier, forKey: kSecAttrGeneric as String)

    } else {
        // load the saved data from Keychain.
        keychainItemData = secItemFormatToDictionary(result!)

    }
}

The only thing I did is to unwrap the outDictionary immediately after getting it.

Claud answered 21/10, 2014 at 23:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.