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
Related
I am having trouble while using SecItemCopyMatching function.
Our function works nearly all devices except only iPhone 13 Pro Max devices.
I searched a lot and found some posts similar to this issue.
People come up with this open source library however I still could not make it work.
https://www.hackingwithswift.com/read/28/3/writing-somewhere-safe-the-ios-keychain
(https://stackoverflow.com/a/58233542/5528870)
They say that they have found the solution via that KeychainWrapper but I could not find the correct way to make it work.
This is our code snippet which works on most of devices except only iPhone 13 Pro Max devices:
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]
var item: CFTypeRef?
let status = SecItemCopyMatching(getquery as CFDictionary, &item)
if (status != errSecSuccess) {
print("No key found")
return false
}
else {
let key = item as! SecKey
self.privateKey = key
print("key = ",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 have tried to use many key combinations. However it never returned errSecSuccess.
Sorry for my lack of information about this topic.
I want to adjust my query so that it can work on all devices.. Thanks in advance
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.
i have the following data structure in my cloud firestore db:
In my data parsing method i'm trying the following:
if document.get("decisions") == nil{
object.decisions = document.get("decisions") as! [String: String]
}
The result is that the app is crashing, because my if clause allows to get the data, even if "decisions" is null
I would say that you would better like to make a safe check, alltogether with casting the type like so:
if let decisions = document.get("decisions") as? [String: String] {
object.decisions = decisions
}
try like this
if document.get("decisions") != nil{
object.decisions = document.get("decisions") as! [String: String]
}
You can do this way which will convert null value to nil and won't cause crash in your app.
func nullToNil(value : AnyObject?) -> AnyObject? {
if value is NSNull {
return nil
} else {
return value
}
}
object.decisions = nullToNil(dict["decisions"])
or you can use
object.decisions = document.get("decisions") as? [String: String]
Hi all I have tried a few solutions but no luck.
I am getting the text from Data Core, but the textview has optional on it.
when it prints it shows optional in the text.
page22TextView?.text = ("\(trans.value(forKey: "page22"))")
can anyone shed light on this ! have tried to unwrap but it stillelow: shows.
the full function is below:
func getTranscriptions () {
//create a fetch request, telling it about the entity
let fetchRequest: NSFetchRequest<TextInputs> = TextInputs.fetchRequest()
do {
//go get the results
let searchResults = try getContext().fetch(fetchRequest)
//I like to check the size of the returned results!
print ("num of results = \(searchResults.count)")
//You need to convert to NSManagedObject to use 'for' loops
for trans in searchResults as [NSManagedObject] {
page22TextView?.text = ("\(trans.value(forKey: "page22"))")
//get the Key Value pairs (although there may be a better way to do that...
print("\(trans.value(forKey: "page22"))")
}
} catch {
print("Error with request: \(error)")
}
}
try to set default value of getting nil value
page22TextView?.text = (trans.value(forKey: "page22") as? String) ?? ""
It'll set your value from trans and if it retrun nill will be set by "".
Hope it'll help you.
try with if-let statement:
if let result = trans.value(forKey: "page22") {
page22TextView?.text = result
}
Or try with guard statement:
guard let result = trans.value(forKey: "page22") else { return }
page22TextView?.text = String(describing: result)
Or you can force upwrap it like:
let result = trans.value(forKey: "page22")
if result != nil {
page22TextView?.text = result! as! String
}
Or you can follow the way suggested by #MrugeshTank below in answers
try to unwrap optional using if let then assign to your textview (if necessary then downcast your value)
if let value = trans.value(forKey: "page22") {
page22TextView?.text = value
}
or
use guard for unwrap
When I try to get my keyChain value, it return a string containing:
Optional("[thing in the KeyChain]")
so, I tried to remove "Optional" by using a loop:
var str = KeychainService.loadToken()
for(var i = 0; i < 9 ; i++)
{
str[i] = ""
}
But i get a error: NSString does not have a member named 'subscript'
The KeychainService class:
import Foundation
import Security
let serviceIdentifier = "MySerivice"
let userAccount = "authenticatedUser"
let accessGroup = "MySerivice"
// Arguments for the keychain queries
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
class KeychainService: NSObject {
/**
* Exposed methods to perform queries.
* Note: feel free to play around with the arguments
* for these if you want to be able to customise the
* service identifier, user accounts, access groups, etc.
*/
internal class func saveToken(token: NSString) {
self.save(serviceIdentifier, data: token)
}
internal class func loadToken() -> NSString? {
var token = self.load(serviceIdentifier)
return token
}
/**
* Internal methods for querying the keychain.
*/
private class 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: [kSecClassGenericPasswordValue, service, userAccount, dataFromString], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecValueDataValue])
// Delete any existing items
SecItemDelete(keychainQuery as CFDictionaryRef)
// Add the new keychain item
var status: OSStatus = SecItemAdd(keychainQuery as CFDictionaryRef, nil)
}
private class func load(service: NSString) -> String? {
// Instantiate a new default keychain query
// Tell the query to return a result
// Limit our results to one item
var keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, userAccount, kCFBooleanTrue, kSecMatchLimitOneValue], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecReturnDataValue, kSecMatchLimitValue])
var dataTypeRef :Unmanaged<AnyObject>?
// Search for the keychain items
let status: OSStatus = SecItemCopyMatching(keychainQuery, &dataTypeRef)
let opaque = dataTypeRef?.toOpaque()
var contentsOfKeychain: String?
if let op = opaque? {
let retrievedData = Unmanaged<NSData>.fromOpaque(op).takeUnretainedValue()
// Convert the data retrieved from the keychain into a string
contentsOfKeychain = NSString(data: retrievedData, encoding: NSUTF8StringEncoding)
} else {
println("Nothing was retrieved from the keychain. Status code \(status)")
}
return contentsOfKeychain
}
}
I just wan't to remove the Optional thing around the str
Or is there a better way to do that?
I have take this code from:
http://matthewpalmer.net/blog/2014/06/21/example-ios-keychain-swift-save-query/
You get the Optional("") because the optional value is not unwrapped. You need to put a ! after the object and you won't get the Optional("") bit any more. I would show you the code but you haven't shown us the print() statement. I made some sample ones below that I think would replicate the problem, though I haven't tried them.
var value:String?
value = "Hello, World"
print("The Value Is \(value)") // Prints "The Value Is Optional(Hello, World)"
print("The Value Is \(value!)")// Prints "The Value Is Hello, World"
Im hoping this answers your question or at least points you in the right direction, just ask if you need more information or a better example.
Here is a Swift 2 example implementation:
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'
You can use the Swift wrapper over the Keychain C API, and avoid the above problems altogether.
https://github.com/deniskr/KeychainSwiftAPI
You will get the Optional("") because the optional value is not unwrapped and if you want to unwrap the optional value to get the string value, do
yourValue.unsafelyUnwrapped
You actually don't even need to do anything. The "Optional" string isn't in the actual data. That is just something Swift seems to place on the output on the console when it is an optional value that isn't unwrapped. IE The data itself doesn't contain the string Optional.
Still, good to unwrap it if you know it contains data.