Read item from keychain right after creating - swift

I'm working with custom keychain in my application and I have problems with reading keychain item right after adding new one. This is code example:
let path = "/tmp/testkeychain_48"
let password = "password"
let serviceName = "service"
let account = "account"
let data = "data".data(using: .utf8)
var keychain: SecKeychain? = nil
var status = SecKeychainCreate(path, UInt32(password.characters.count), password, false, nil, &keychain)
assert(status == errSecSuccess)
var query: [String: AnyObject] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: serviceName as AnyObject,
kSecAttrAccount as String: account as AnyObject,
kSecValueData as String: data as AnyObject,
kSecUseKeychain as String: keychain as AnyObject,
]
var item: CFTypeRef? = nil
status = SecItemAdd(query as CFDictionary, &item)
assert(status == errSecSuccess)
assert(item != nil)
query = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: serviceName as AnyObject,
kSecAttrAccount as String: account as AnyObject,
kSecMatchLimit as String: kSecMatchLimitAll as AnyObject,
kSecUseKeychain as String: keychain as AnyObject,
]
status = SecItemCopyMatching(query as CFDictionary, &item)
assert(status == errSecSuccess)
assert(item != nil)
try? FileManager.default.removeItem(atPath: path)
SecItemAdd(_:_) returns success but SecItemCopyMatching(_:_) fails with error The specified item could not be found in the keychain. With default keychain all works correctly.

Related

EXC_BAD_ACCESS code=257 at SecKeyIsAlgorithmSupported

I have a KeyChain class where I sign a string.
I got Thread 8: EXC_BAD_ACCESS (code=257, address=0x3fd574bc6a7ef9db) error at SecKeyIsAlgorithmSupported function. I could not figure out why this error pops up.
When I use the getquery variable which is commented it all works fine except on iPhone 13 pro max devices. So I wanted to try different queries hoping that can work on all devices. But in that case SecKeyIsAlgorithmSupported function crashes giving this error EXC_BAD_ACCESS. Here is the function I use.
func signString(clearString:String) -> Bool {
/*let getquery: [String: Any] = [kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: serviceName,
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
kSecReturnRef as String: true]*/
let getquery: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: serviceName,
kSecReturnAttributes as String: kCFBooleanTrue!,
kSecMatchLimit as String: kSecMatchLimitAll]
var item: CFTypeRef?
let status = SecItemCopyMatching(getquery as CFDictionary, &item)
print("status = ",status)
if (status != errSecSuccess) {
print("No key found")
return false
}
else {
let key = item as! SecKey
self.privateKey = key
let data = clearString.data(using: .utf8)! as CFData
let algorithm: SecKeyAlgorithm = .ecdsaSignatureMessageX962SHA256
if (self.privateKey != nil) {
guard SecKeyIsAlgorithmSupported(self.privateKey!, .sign, algorithm) else {
print("Algorithm Not Supported")
return false
}
var error: Unmanaged<CFError>?
guard let signature = SecKeyCreateSignature(self.privateKey!,algorithm, data, &error) as Data? else {
print("signature error")
return false
}
self.signedString = signature.base64EncodedString()
return true
}
else {
print("Private Key is null")
return false
}
}
}
I wish there would be a way to avoid this crash. I searched about it but I could not find a way to fix that.
Any help will be appreciated. Thanks in advance.
Your get query states kSecMatchLimitAll, which will result in a CFArray object as a result. You can easily fix that by changing it to kSecMatchLimitOne, or you can loop the list, by casting it to an array.
let keys = item as! [SecKey]
for key in keys {
SecKeyIsAlgorithmSupported(key, .sign, . ecdsaSignatureMessageX962SHA256)
}
Do note that not all generic items, or likely none, are valid SecKey objects. It appears you're using ECC keys, which can be stored using the kSecClass: kSecClassKey attribute. I would highly recommend storing it as what it is, instead of storing it as a generic password (kSecClassGenericPassword) as you're doing right now

Cannot get symmetric key from keychain

Try to create and retreive a symmetric key from keychain :
Add the key
let key = Data(repeating: 0xee, count: 32)
let name = "test"
let attributes = [
kSecAttrKeyType: kSecAttrKeyTypeAES,
kSecAttrKeySizeInBits: NSNumber(value: 256)
] as CFDictionary
var error: Unmanaged<CFError>?
let secKey = SecKeyCreateFromData(attributes, key as CFData, &error)
let addquery = [
kSecClass: kSecClassKey,
kSecAttrKeyClass: kSecAttrKeyClassSymmetric,
kSecAttrLabel: name,
kSecValueRef: secKey!
] as CFDictionary
let status = SecItemAdd(addquery as CFDictionary, nil)
if status != errSecSuccess {
print(SecCopyErrorMessageString(status, nil)!)
}
The keychain item is created
Get the key
let name = "test"
let getquery = [
kSecClass: kSecClassKey,
kSecAttrKeyClass: kSecAttrKeyClassSymmetric,
kSecAttrLabel: name
] as [CFString : Any]
var secKey: CFTypeRef?
let status = SecItemCopyMatching(getquery as CFDictionary, &secKey)
if status == errSecSuccess {
if let dic = SecKeyCopyAttributes(secKey as! SecKey) as? [CFString: Any] {
if let key = dic[kSecValueData] {
print("Ok")
} else {
print("Cannot get the key")
}
} else {
print("Error retrieving dictionnary")
}
} else {
print(SecCopyErrorMessageString(status, nil)!)
}
If the key is added and retrieve in the same run it works. The number of elements in the dic is 21.
But if i only try to get the key stored in keychain i get the dictionary but not the key. The number of elements in the dic is 20 (kSecValueData is missing).
What parameters are missing to get the key ?
Thank you
In order to retrieve your key from the KeyChain, you should also specify the kSecAttrAccessible option:
let attributes = [
kSecAttrKeyType: kSecAttrKeyTypeAES,
kSecAttrKeySizeInBits: NSNumber(value: 256),
kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked
] as CFDictionary
This one can be queried and retrieved when the Mac is unlocked.
You can then use the code you already provided
let getquery = [
kSecClass: kSecClassKey,
kSecAttrKeyClass: kSecAttrKeyClassSymmetric,
kSecAttrLabel: name,
kSecMatchLimit: kSecMatchLimitAll
] as [CFString : Any]
var secKeys: CFTypeRef?
let status = SecItemCopyMatching(getquery as CFDictionary, &secKeys)
if status == errSecSuccess {
for symmetricKey in (secKeys as! [SecKey]) {
guard let keyAttributes = SecKeyCopyAttributes(symmetricKey) as? [CFString: Any] else {
fatalError("No key attributes for symmetric key")
}
guard let keyData = keyAttributes[kSecValueData] as? Data else {
fatalError("No key data for symmetric key")
}
print("Key data retrieved: \(keyData.base64EncodedString())")
}
}
Now, you might encounter the old keys you've added, which still will not return any data (as their accessibility flag is set incorrectly). Remove those using the Keychain Access application on your Mac. After that, you should be able to add an AES key and retrieve an AES key.

swift keychain add, delete, get working, update not working

I'm troubleshooting a problem with Keychain updates. I have SecItemAdd, SecItemCopyMatching, and SecItemDelete working. But I'm stumped on SecItemUpdate. I always get a -50 fail.
I've put a public MacOS keychainTest project on Github at:
https://github.com/darrellroot/keychainTest/blob/master/README.md
FYI this almost identical question/answer should help me, but it's not:
Swift 4 Keychain SecItemUpdate Error status code -50
Here's relevant code snippets:
let keychainTest = "Keychain Test"
let serverHostname = "mail.nowhere.com"
let keychainService = "KeychainService"
AddQuery works:
let addQuery: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: keychainService,
kSecAttrAccount as String: serverHostname,
kSecValueData as String: inputTextOutlet.stringValue]
let status = SecItemAdd(addQuery as CFDictionary, nil)
GetQuery works:
let getQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: keychainService,
kSecAttrAccount as String: serverHostname,
kSecMatchLimit as String: kSecMatchLimitOne,
kSecReturnData as String: true]
var rawResult: AnyObject?
let status = SecItemCopyMatching(getQuery as CFDictionary, &rawResult)
statusLabel.stringValue = status.description
guard let retrievedData = rawResult as? Data else { return }
guard let pass = String(data: retrievedData, encoding: String.Encoding.utf8) else { return }
DeleteQuery works:
let deleteQuery: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: keychainService,
kSecAttrAccount as String: serverHostname]
let status = SecItemDelete(deleteQuery as CFDictionary)
statusLabel.stringValue = status.description
But for this updateQuery I always get a -50 OSStatus message:
let updateQuery: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: keychainService,
kSecAttrAccount as String: serverHostname]
let newAttributes: [String: Any] = [kSecValueData as String: inputTextOutlet.stringValue]
let status = SecItemUpdate(updateQuery as CFDictionary, newAttributes as CFDictionary)
per osstatus.com, -50 is "One or more parameters passed to the function were not valid."

Cannot save Security Item to Keychain

I am trying to save SecIdentity item into the keychain using Swift 3. After saving status of operation is alway 0 (Successful), but when I try to retrieve the saved one, status is -25300 (which means that object does not exist) What I'm doing wrong?
func saveIdentity(identity: SecIdentity) -> Data? {
let str = "identity"
let saveQuery = [
kSecClass as String : kSecClassIdentity,
kSecValueRef as String : identity,
kSecAttrLabel as String : "identity",
kSecAttrAccessible as String : kSecAttrAccessibleAlways
] as [String : Any]
var item: CFTypeRef? = nil
var status: OSStatus = SecItemAdd(saveQuery as CFDictionary, nil)
// Status = 0
let loadQuery = [
kSecClass as String : kSecClassIdentity,
kSecAttrLabel as String : "identity",
kSecReturnRef as String : kCFBooleanTrue,
kSecMatchLimit as String : kSecMatchLimitAll
] as [String : Any]
status = SecItemCopyMatching(loadQuery as CFDictionary, &item)
// Status = -25300
if status == noErr {
return item as! Data?
} else {
return nil
}
}

Trying to use KeychainItemWrapper by Apple "translated" to Swift

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)
I have tried commenting out the problem lines and then I get this error at another if():
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.
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:"");
}
official is GenericKeychain
existing several swift version, the best one is:
jrendel/SwiftKeychainWrapper ยท GitHub
how to use it:
download file: KeychainWrapper.swift
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
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'
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.