Error when adding/updating keychain value with kSecClass - swift

I'm trying to add/update a keychain value using the dictionary below:
public var itemDictionary: [String: Any] {
let dictionary: [CFString: Any?] = [
kSecAttrAccessGroup: accessGroup, // nil
kSecAttrService: service, // nil
kSecClass: kSecClassGenericPassword,
kSecAttrAccount: account,
kSecAttrSynchronizable: synchronizable, // false
kSecAttrAccessible: access.queryValue,
kSecValueData: data
]
return dictionary.compactMapValues { $0 } as [String: Any]
}
However, both SecItemAdd and SecItemUpdate fail with an error of -25303 (errSecNoSuchAttr). After some debugging, I figured out that if I removed kSecClass from the dictionary above, that both SecItemAdd and SecItemUpdate work as expected. This doesn't seem right though as my understanding is that kSecClass is a required field. What am I doing wrong here?

Related

How to get the realy fixed Device-ID in swift?

I use the below code since long time. I have thought it is unique
But I have deleted my app and reinstalled it, I get new different Device-ID.
if let uuid = UIDevice.current.identifierForVendor?.uuidString {
print(uuid)
}
every new reinstall, I get a new ID.
How do I get something which stays the same?
Since the value returned from identifierForVendor can be cleared when deleting the app or reset if the user resets it in the Settings app, you have to manage persisting it yourself.
There are a few ways to accomplish this. You can setup a server that assigns a uuid which is then persisted and fetched server side via user login, or you can create and store it locally in the keychain.
Items stored in the keychain will not be deleted when the app is deleted. This allows you to check if a uuid was previously stored, if so you can retrieve it, if not you can generate a new uuid and persist it.
Here's a way you could do it locally:
/// Creates a new unique user identifier or retrieves the last one created
func getUUID() -> String? {
// create a keychain helper instance
let keychain = KeychainAccess()
// this is the key we'll use to store the uuid in the keychain
let uuidKey = "com.myorg.myappid.unique_uuid"
// check if we already have a uuid stored, if so return it
if let uuid = try? keychain.queryKeychainData(itemKey: uuidKey), uuid != nil {
return uuid
}
// generate a new id
guard let newId = UIDevice.current.identifierForVendor?.uuidString else {
return nil
}
// store new identifier in keychain
try? keychain.addKeychainData(itemKey: uuidKey, itemValue: newId)
// return new id
return newId
}
And here's the class for storing/retrieving from the keychain:
import Foundation
class KeychainAccess {
func addKeychainData(itemKey: String, itemValue: String) throws {
guard let valueData = itemValue.data(using: .utf8) else {
print("Keychain: Unable to store data, invalid input - key: \(itemKey), value: \(itemValue)")
return
}
//delete old value if stored first
do {
try deleteKeychainData(itemKey: itemKey)
} catch {
print("Keychain: nothing to delete...")
}
let queryAdd: [String: AnyObject] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: itemKey as AnyObject,
kSecValueData as String: valueData as AnyObject,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked
]
let resultCode: OSStatus = SecItemAdd(queryAdd as CFDictionary, nil)
if resultCode != 0 {
print("Keychain: value not added - Error: \(resultCode)")
} else {
print("Keychain: value added successfully")
}
}
func deleteKeychainData(itemKey: String) throws {
let queryDelete: [String: AnyObject] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: itemKey as AnyObject
]
let resultCodeDelete = SecItemDelete(queryDelete as CFDictionary)
if resultCodeDelete != 0 {
print("Keychain: unable to delete from keychain: \(resultCodeDelete)")
} else {
print("Keychain: successfully deleted item")
}
}
func queryKeychainData (itemKey: String) throws -> String? {
let queryLoad: [String: AnyObject] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: itemKey as AnyObject,
kSecReturnData as String: kCFBooleanTrue,
kSecMatchLimit as String: kSecMatchLimitOne
]
var result: AnyObject?
let resultCodeLoad = withUnsafeMutablePointer(to: &result) {
SecItemCopyMatching(queryLoad as CFDictionary, UnsafeMutablePointer($0))
}
if resultCodeLoad != 0 {
print("Keychain: unable to load data - \(resultCodeLoad)")
return nil
}
guard let resultVal = result as? NSData, let keyValue = NSString(data: resultVal as Data, encoding: String.Encoding.utf8.rawValue) as String? else {
print("Keychain: error parsing keychain result - \(resultCodeLoad)")
return nil
}
return keyValue
}
}
Then you can just have a user class where you get the identifier:
let uuid = getUUID()
print("UUID: \(uuid)")
If you put this in a test app in viewDidLoad, launch the app and note the uuid printed in the console, delete the app and relaunch and you'll have the same uuid.
You can also create your own completely custom uuid in the app if you like by doing something like this:
// convenience extension for creating an MD5 hash from a string
extension String {
func MD5() -> Data? {
guard let messageData = data(using: .utf8) else { return nil }
var digestData = Data(count: Int(CC_MD5_DIGEST_LENGTH))
_ = digestData.withUnsafeMutableBytes { digestBytes in
messageData.withUnsafeBytes { messageBytes in
CC_MD5(messageBytes, CC_LONG(messageData.count), digestBytes)
}
}
return digestData
}
}
// extension on UUID to generate your own custom UUID
extension UUID {
static func custom() -> String? {
guard let bundleID = Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String else {
return nil
}
let unique = bundleID + NSUUID().uuidString
let hashData = unique.MD5()
let md5String = hashData?.map { String(format: "%02hhx", $0) }.joined()
return md5String
}
}
Note that to use the MD5 function you will have to add the following import to an Objective-C bridging header in your app: (if you're building with Xcode < 10. In Xcode 10+ CommonCrypto is included so you can skip this step)
#import <CommonCrypto/CommonCrypto.h>
If your app does not have a bridging header, add one to your project and make sure to set it in build settings:
Once it's setup you can generate your own custom uuid like this:
let otherUuid = UUID.custom()
print("Other: \(otherUuid)")
Running the app and logging both outputs generates uuids something like this:
// uuid from first example
UUID: Optional("8A2496F0-EFD0-4723-8C6D-8E18431A49D2")
// uuid from second custom example
Other: Optional("63674d91f08ec3aaa710f3448dd87818")
Unique Id in iPhone is UDID, which is not accessible in current version of OS, because it can be misused. So Apple has given the other option for the unique key but it change every time you install the app.
--Cannot access UDID
But there is another way to implement this feature.
First you have to generate the Unique ID :
func createUniqueID() -> String {
let uuid: CFUUID = CFUUIDCreate(nil)
let cfStr: CFString = CFUUIDCreateString(nil, uuid)
let swiftString: String = cfStr as String
return swiftString
}
After getting the this which is unique but changes after the app install and reinstall.
Save this id to the Key-Chain on any key let say "uniqueID".
To save the key in keyChain :
func getDataFromKeyChainFunction() {
let uniqueID = KeyChain.createUniqueID()
let data = uniqueID.data(using: String.Encoding.utf8)
let status = KeyChain.save(key: "uniqueID", data: data!)
if let udid = KeyChain.load(key: "uniqueID") {
let uniqueID = String(data: udid, encoding: String.Encoding.utf8)
print(uniqueID!)
}
}
func save(key: String, data: Data) -> OSStatus {
let query = [
kSecClass as String : kSecClassGenericPassword as String,
kSecAttrAccount as String : key,
kSecValueData as String : data ] as [String : Any]
SecItemDelete(query as CFDictionary)
return SecItemAdd(query as CFDictionary, nil)
}
Next when you required to perform any task based on uniqueID ,first check if any data is saved in Key-Chain on key "uniqueID" .
Even if you uninstall the app , the key-chain data is still persisted, it will delete by OS.
func checkUniqueID() {
if let udid = KeyChain.load(key: "uniqueID") {
let uniqueID = String(data: udid, encoding: String.Encoding.utf8)
print(uniqueID!)
} else {
let uniqueID = KeyChain.createUniqueID()
let data = uniqueID.data(using: String.Encoding.utf8)
let status = KeyChain.save(key: "uniqueID", data: data!)
print("status: ", status)
}
}
In this way you can generate the uniqueID once and use this id every time.
NOTE: But when you upload next version of your app, upload it with the same Provisioning Profile otherwise you cannot access the key-chain store of your last installed app.
Key-Chain Store is associated with the Provisioning profile.
Check Here
Access to the unique device id (UDID) has been disallowed for ages now. identifierForVendor is its replacement, and its behaviour has always been documented.

Error adding Key item to macOS keychain

I have following code:
let keyData = UUID().uuidString.data(using: .utf8)!
var attributes: [NSString: Any] = [
kSecClass: kSecClassKey,
kSecAttrApplicationTag: keyData,
]
let st1 = SecItemDelete(attributes as CFDictionary)
attributes[kSecValueData] = keyData
let st2 = SecItemAdd(attributes as CFDictionary, nil)
I am trying to add item to the keychain with type kSecClassKey. For some reason this code works perfectly in iOS and doesn't work in macOS.
In macOS st1 is -25300 (which means The item cannot be found.) and st2 is -25299 (which means The item already exists.)
What can I do to make this code work?
The error errSecDuplicateItem (-25299) might also be returned if you miss a mandatory attribute, e.g., if you try to add a kSecClassGenericPassword key without the kSecAttrService set.
In your case I wonder why you try to store the UUID as a cryptographic key (kSecClassKey). Storing it as a generic password (kSecClassGenericPassword) instead would suffice.
let keyData = UUID().uuidString.data(using: .utf8)!
var attributes: [NSString: Any] = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: "YourApp-UUID", // Determines the purpose/context of the used password/value
kSecAttrLabel: "YourApp (UUID)", // Name of the Keychain item
kSecValueData: keyData, // Actual value, that will be stored securely
]
let status = SecItemAdd(attributes as CFDictionary, nil)

osx macOS public key import to keychain duplicate

I am trying to permanently import an RSA public key into the keychain
let params: [NSString: AnyObject] = [
kSecClass: kSecClassKey,
kSecAttrKeyType: kSecAttrKeyTypeRSA,
kSecAttrApplicationTag: tag as AnyObject,
kSecValueData: data as AnyObject,
kSecReturnPersistentRef: true as AnyObject
]
let persistKey = UnsafeMutablePointer<AnyObject?>(mutating: nil)
let status = SecItemAdd(params as CFDictionary, persistKey)
....
Unfortunately the key (regardless which) gets errSecDuplicateItem but if I select it with SecItemCopyMatching.... i get errSecItemNotFound
I tried multiple solutions like adding different attributes like account etc.. but most of this eventually resulted in unknown parameter errors.
I'm sure the key is correct cause SecKeyCreateWithData is fine with it.
edit:
Thats the way to collect the key:
let params: [NSString: AnyObject] = [
kSecClass: kSecClassKey,
kSecAttrKeyType: kSecAttrKeyTypeRSA,
kSecAttrApplicationTag: tag as AnyObject,
kSecReturnRef: true as AnyObject
]
var keyRef: AnyObject? = nil
status = SecItemCopyMatching(params, &keyRef)
The key is also not findable if you look it up manually in the keychain app
I had the same issue. The code worked fine for me on iOS but I could not get it to work for macOS either.
I then rewrote the code by using Apple's documentation https://developer.apple.com/documentation/security/certificate_key_and_trust_services/keys/storing_keys_as_data
So the code to get a SecKeyRef from a public key given as Data is just:
let options: [String: Any] = [kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
kSecAttrKeySizeInBits as String : 2048]
var error: Unmanaged<CFError>?
guard let key = SecKeyCreateWithData(data as CFData,
options as CFDictionary,
&error) else {
throw error!.takeRetainedValue() as Error
}

Handling Swift Dicts: fatal error: can't unsafeBitCast between types of different sizes

I have a function in Swift that needs to be able to handle multiple types. Specifically, it needs to be able to parse both Dicts and Strings.
The problem I have is the Dicts could be several types, depending on their origin. So I could be provided with [String:Any] or [String:String] (coming from Swift) or [String:AnyObject] (coming from objc). The top level parsing function takes Any, which it then tests for specific types and attempts to parse them.
At first I just tried testing for if let dict = object as? [String:Any], but if I passed in another type [String:AnyObject] or [String:String] it failed. So I tried testing each type:
func parseLink(object: Any) {
if let dict = object as? [String:Any] {
return self.parseDict(dict)
} else if let dict = object as? [String:AnyObject] {
return self.parseDict(dict)
} else if let dict = object as? [String:String] {
return self.parseDict(dict)
} else if let string = object as? String {
return parseURL(string)
}
}
func parseDict(dict: [String:Any]) { ..... }
So I've created some Unit Tests to test the behavior:
func testDictTypes() {
let testDict: [String:Any] = [ "orgId" : "123456789" ]
let link = OrgContextLinkParser().parseLink(testDict)
XCTAssertNotNil(link)
let testDict1: [String:AnyObject] = [ "orgId" : "123456789" ]
let link2 = OrgContextLinkParser().parseLink(testDict1)
XCTAssertNotNil(link2)
let testDict3: [String:String] = [ "orgId" : "123456789" ]
let link3 = OrgContextLinkParser().parseLink(testDict3)
XCTAssertNotNil(link3)
}
This all compiles fine, but I get a fatal runtime error if a [String:AnyObject] is passed in. This is troubling since Swift's type system is supposed to prevent these kind of errors and I get no warning or errors thrown when I compile.
I also really don't want to duplicate the exact same logic multiple times just to handle different dict types. I.E., handling [String:Any], [String:AnyObject] and [String:String] have virtually the exact same logic.
The only possible solution I've seen is to actually duplicate the dictionary, which seems rather expensive (Convert [String: AnyObject] to [String: Any]). For performance reasons, it seems better to just copy paste the code and change the function signatures... but really!? That's seems excessive.
The best solution seems to be to parse a Dict as [String:AnyObject] and copy the value only if it's [String:Any]:
if let dict = object as? [String:Any] {
var objDict: [String:AnyObject] = [:]
for (key, value) in dict {
if let obj = value as? AnyObject {
objDict[key] = obj
}
}
return self.parseDict(objDict)
I don't particularly like this, but so far it's be best I've been able to come up with.
Does anyone have any idea how to handle this properly? I'm especially concerned that I can cast Any as [String:AnyObject], pass it to a function that takes [String:Any] and I get no compiler errors, even though it crashes at runtime.

Swift - Adding data to a dictionary

So I have created a function which I use for all my requests, which will retry the request if fails and also add some of my headers to all requests for security.
The problem I am having is if data is nil, no values are being set in data.
Where am I going wrong here?
func performAndRetryRequestWithURL(method: Alamofire.Method, url: String, parameters: [String: AnyObject]?, completionHandler:(AnyObject?) -> Void) {
var data: [String: AnyObject]?
if (parameters != nil) {
data = parameters!
} else {
data = [String: AnyObject]?()
}
var unixTime = NSDate().timeIntervalSince1970
// I tried both ways to add timestamp
data?["timestamp"] = unixTime
data?.updateValue(unixTime, forKey: "timestamp")
data?.updateValue(hash("myfakekey"), forKey: "key")
println(data)
If parameters has data it seems to append, but if parameters is nil, data will be nil also.
Make data not optional:
var data = parameters ?? [String: AnyObject]()
If parameters is not nil, data will be assigned to parameter's unwrapped value. Otherwise, it will be initialized to an empty, non-optional dictionary.
The problem is in this line:
data = [String: AnyObject]?()
This looks at first glance like it should create an empty dictionary of type [String: AnyObject], but what it actually does is create an Optional dictionary that is initialized to nil. Get rid of the ? and it should work:
data = [String: AnyObject]()
Or, alternatively, since you've already identified data as being a dictionary of type [String: AnyObject]?, you could simply write:
data = [:]
That would create an empty dictionary as well.