GRme
GRme

Reputation: 2757

Keychain class doesn't work for iPhone 4s/5 and iPad

I save a userId as token with keychain on the device in my iOS app. This works fine for iPhone 5s/6/6 Plus, but not for iPhone 4s/5 and iPad. On stating my app I check if a userId exists in keychain or not with func tokenExists(). But then I get an error:

fatal error: unexpectedly found nil while unwrapping an Optional value 

as seen here:

error message

And here is my keychain class to save, load and delete a token and to check if a token exists:

import UIKit
import Security

// Identifiers
let userAccount:String = "userAccount"

class KeychainService: NSObject {

/*
save token to keychain
*/
func deleteToken() {
    self.delete("userId")
}

/*
save token to keychain
*/
func saveToken(token: NSString) {
    self.save("userId", data: token)
}

/*
load keychain token
*/
func loadToken() -> NSString? {
    var token: AnyObject? = self.load("userId")

    return token as? NSString
}

func tokenExists() -> Bool {

    let token:NSString = loadToken()!

    if (token == "") {
        return false
    }
    else {
        return true
    }
}

private func delete(service: NSString) {
    //var dataFromString: NSData = data.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
    // Instantiate a new default keychain query
    var keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPassword, service, userAccount], forKeys: [kSecClass, kSecAttrService, kSecAttrAccount])

    // Delete any existing items
    SecItemDelete(keychainQuery as CFDictionaryRef)

    // Check that it worked ok
    //println("Token deleted")
}

private func save(service: NSString, data: NSString) {
    var dataFromString: NSData = data.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
    // Instantiate a new default keychain query
    var keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPassword, service, userAccount, dataFromString], forKeys: [kSecClass, kSecAttrService, kSecAttrAccount, kSecValueData])

    // Delete any existing items
    SecItemDelete(keychainQuery as CFDictionaryRef)

    // Add the new keychain item
    var status: OSStatus = SecItemAdd(keychainQuery as CFDictionaryRef, nil)

    // Check that it worked ok
    //println("Saving status code is: \(status)")
}

private func load(service: NSString) -> AnyObject? {
    // Instantiate a new default keychain query
    // Tell the query to return a result
    // Limit our results to one item
    var keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPassword, service, userAccount, kCFBooleanTrue, kSecMatchLimitOne], forKeys: [kSecClass, kSecAttrService, kSecAttrAccount, kSecReturnData, kSecMatchLimit])

    // I'm not too sure what's happening here...
    var dataTypeRef :Unmanaged<AnyObject>?

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

    //println("Loading status code is: \(status)")

    // I'm not too sure what's happening here...
    let opaque = dataTypeRef?.toOpaque()
    var contentsOfKeychain: NSString?

    if let op = opaque? {
        let retrievedData = Unmanaged<NSData>.fromOpaque(op).takeUnretainedValue()
        //println("Retrieved the following data from the keychain: \(retrievedData)")
        var str = NSString(data: retrievedData, encoding: NSUTF8StringEncoding)
        contentsOfKeychain = NSString(data: retrievedData, encoding: NSUTF8StringEncoding)
        //println("The decoded string is \(str)")
    } else {
        //println("Nothing was retrieved from the keychain.")
    }

    return contentsOfKeychain
}
}

Are there any solutions for my problem?

Upvotes: 0

Views: 226

Answers (2)

cscott530
cscott530

Reputation: 1708

As of updates included in XCode 6.1, I now have to compare the status to errSecItemNotFound:

var dataTypeRef: Unmanaged<AnyObject>?
let status: OSStatus = SecItemCopyMatching(keychainQuery as CFDictionaryRef, &dataTypeRef)
if (status != errSecItemNotFound) {
    if let dataType = dataTypeRef? {
        ...
    }
}

Up until this update, I was also doing if let op = opaque? {, and it was properly catching it was nil, but then I started getting errors for attempting to unwrap a nil value.

Hope this helps anyone else that stumbles upon this answer with the same issue.

Upvotes: 1

Daniel T.
Daniel T.

Reputation: 33967

Your problem is that your tokenExists() method doesn't check to see if the token exists. Instead it assumes the tokenExists (as noted by the ! at the end of let token:NSString = loadToken()!) and then checks only if it is an empty string.

Try this tokenExists() method:

func tokenExists() -> Bool {
    var result = false
    if let token = loadToken() {
        // the token exists, but is it blank?
        if token != "" {
            // no, the token is not blank
            result = true
        }
    }
    return result
}

Upvotes: 0

Related Questions