Error when saving to keychain using SecItemAdd - swift

I'm getting an error saving an encoded value to keychain at the point of SecItemAdd. I'm fairly new to working with Keychain and not sure how to return the error to see what I'm doing incorrectly.
let encoder = JSONEncoder()
func initiateLogin(forceReconnect: Bool = false, completion: #escaping (Bool)->Void) {
Task {
await loginUser(forceReconnect: forceReconnect, completion: { user in
if let encoded = try? self.encoder.encode(user) {
// MARK: - keychain
let attributes: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "johnDoe",
kSecValueData as String: encoded,
]
if SecItemAdd(attributes as CFDictionary, nil) == noErr {
print("\(#function) 😀 user saved successfully in keychain")
} else {
print("\(#function) ⚠️ something went wrong")
}
self.initClient(withCredentials: user)
completion(true)
}
})
}
}

You didn't specify which error you are getting (it should be a return value of SecItemAdd), but the most common mistake is this: as documentation states:
The operation might fail, for example, if an item with the given attributes already exists.
In other words: your code will only work once for each unique kSecAttrAccount.
Instead, you need to check if an item already exists, and if yes, update it (or delete the previous one and create a new one).
How to update the items or delete them is explained here.
Side note: it's also a good idea to put keychain management into a separate class (a wrapper), which you can call from anywhere in your code to save / load data from keychain. Here's a good tutorial on how to create such wrapper.

Related

Firebase realtime database query doesn't get new data until app reinstalled

In my app on sign up I'm checking if username is already taken.
I install the app, go to sign up, check if the username is free and everything works fine. If username is taken it tells me that.
But then when I created the account and trying to create another one with the same username, for some reason this username cant be found in database, even tho it's there.
Here is the code I use:
func singleObserveUser(withUsername username: String, completion: #escaping (UserModel) -> Void, onError: #escaping (String) -> Void) {
let queryUsername = username.lowercased().trimmingCharacters(in: .whitespaces)
usersRef.queryOrdered(byChild: Constants.UserData.UsernameLowercased).queryEqual(toValue: queryUsername).observeSingleEvent(of: .value) { (snapshot) in
if let _ = snapshot.value as? NSNull {
onError("No userdata found")
} else {
if let dict = snapshot.value as? [String: Any] {
let user = UserModel.transformDataToUser(dict: dict, id: snapshot.key)
completion(user)
} else {
onError("No userdata found")
}
}
}
}
If I restart app - everything is still the same.
If I delete app and install it again - everything works fine.
Seams like Firebase save some data on phone.
Thank you for your help!
If you have disk persistence enabled (the default on iOS and Android), the client stores data it has recently seen to allow it faster lookup later, and observeSingleEvent(of returns the value from that cache indeed.
If you want to ensure you have the latest value from the server, use getData instead as shown in the documentation on getting data once. Also check out Firebase Offline Capabilities and addListenerForSingleValueEvent for a longer explanation of the behavior.

Where does SecKeyCreateSignature get the key name for Keychain signing authorization dialog?

I have noticed a difference between certain keys in the Keychain with respect to how they appear in the Keychain signing dialog, and I cannot figure out why some are displayed a certain way while others are not.
Here is some test code to use identities in the Keychain to sign a sample bit of data.
func testCreateSignature() throws {
let query: [String: Any] = [kSecClass as String: kSecClassIdentity,
kSecMatchLimit as String: kSecMatchLimitAll,
kSecReturnAttributes as String: false,
kSecReturnRef as String: true,
kSecReturnData as String: true]
var resultsRef: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &resultsRef)
guard status == errSecSuccess else { throw SecurityError.unhandledError(status: status) }
guard let results = resultsRef as? [[String:Any]] else {
throw SecurityError.unexpectedCertificateData
}
let data = Data([0xDE, 0xAD, 0xBE, 0xEF])
var privateKey: SecKey!
for result in results {
let secIdentity = result[kSecValueRef as String] as! SecIdentity
try SecIdentityCopyPrivateKey(secIdentity, &privateKey).check()
var error: Unmanaged<CFError>?
let signature = SecKeyCreateSignature(privateKey, .rsaSignatureMessagePKCS1v15SHA1, data as CFData, &error)!
if let error = error {
throw error.takeRetainedValue()
}
print(signature)
}
}
When the code attempts to use one of the keys that Xcode installed for code signing, the resulting dialog looks like the following:
However, when the code attempts to use a key that I've installed, no matter what the label on the key in the Keychain is, it always looks like this:
When my app attempts to use a key to sign, I would like the user to see the name of the key the app wants to use, instead of just generic "privateKey", but I cannot find where this information might be stored on the key.
I have checked the kSecAttrLabel and kSecAttrApplicationLabel attributes of both identities and the private keys and cannot find the text that appears in the dialogs.
I found it. It is a property of the Access Control List of a Keychain item. See 'descriptor' param for SecAccessCreate.
If you do not specify a custom ACL when importing a key, it will default to "privateKey".
I was using SecPKCS12Import to import a .pfx file. I attempted to set the kSecImportExportAccess key in the options parameter to a custom SecAccess object, but it would always import with a default ACL.
I ended up refactoring the code to use SecItemImport instead to import the .pfx file and supplied a custom SecAccess instance:
static func importIdentity(contentsOf url: URL, password: String) throws {
let data = try Data.init(contentsOf: url)
var access: SecAccess!
try SecAccessCreate("License Key" as CFString, nil, &access).check()
var keychain: SecKeychain!
var outItems: CFArray?
let filename: CFString? = url.isFileURL ? url.lastPathComponent as CFString : nil
var inputFormat: SecExternalFormat = .formatPKCS12
var itemType: SecExternalItemType = .itemTypeAggregate
let unmanagedPassword = Unmanaged<AnyObject>.passRetained(password as AnyObject)
let unmanagedAccess = Unmanaged<SecAccess>.passRetained(access)
var params: SecItemImportExportKeyParameters = SecItemImportExportKeyParameters(version: UInt32(SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION),
flags: .importOnlyOne,
passphrase: unmanagedPassword,
alertTitle: nil,
alertPrompt: nil,
accessRef: unmanagedAccess,
keyUsage: nil,
keyAttributes: nil)
try SecKeychainCopyDefault(&keychain).check()
try SecItemImport(data as CFData, filename, &inputFormat, &itemType, [], &params, keychain, &outItems).check()
}
Importing the identity as above will result in "License Key" being shown in the signing dialog rather than "privateKey".

Swift 4 - How can I call a piece of code which returns out of the function in multiple functions without duplicating code?

I am working with a poorly designed API (I don't have control over it) where even if the access token is expired, it still returns a HTTP success code but includes the 401 Unauthorized in the actual response body. So simply checking the HTTP status code isn't sufficient and I need to check the actual response.
I am making many network requests in my app to this API and when I receive the response, I need to first check whether the response is an array or a dictionary. If array, then we are good. If it's a dictionary, then I need to check the "error" field in the response dictionary which will have the 401 Unauthorized.
So every time I receive the JSON response, I have the following piece of code to return out of the function if it's an error dictionary:
guard !(myJSON is NSDictionary) && (myJSON as! NSDictionary).value(forKey: "error") != nil else {
print("Error: ", MyAppError.accessTokenExpired)
return
}
I am wondering if there is a way to prevent duplicating this piece of code in every network request function I have? Can I have this somewhere and simply call it without duplicating these lines of code each time?
Wrap it in a function like this one
func isErrorResponse(_ response: Any) -> Bool {
if let dict = response as? [String: Any], let error = dict["error"] {
print("Error: \(MyAppError.accessTokenExpired)")
return true
}
return false
}
You should use the swift data types if you are using Swift language. Although if myJSON validates with Dictionary then it will definitely be validated with [String: Any].
Create function like:
func isValidResponse(_ json: Any) -> Bool {
guard let jsonDict = json as? [String: Any], let let error = dict["error"] else { return true }
print("Error: \(error.localizedDescription)")
return false
}
Use it as:
guard YourClass.isValidResponse(myJSON) else { return }
// Valid Response - Code here...

Sandbox and Finder alias

I'm trying to create a security scoped URL for a user provide file which happens to be an alias using this methods which uses a resolvedFinderAlias() method:
func storeBookmark(url: URL) -> Bool
{
// Resolve alias before storing bookmark
let origURL = (url as NSURL).resolvedFinderAlias()
// Peek to see if we've seen this key before
if let data = bookmarks[url] {
if self.fetchBookmark(key: url, value: data) {
Swift.print ("= \(url.absoluteString)")
return true
}
}
do
{
let options:URL.BookmarkCreationOptions = [.withSecurityScope,.securityScopeAllowOnlyReadAccess]
let data = try url.bookmarkData(options: options, includingResourceValuesForKeys: nil, relativeTo: origURL)
bookmarks[url] = data
return self.fetchBookmark(key: url, value: data)
}
catch let error
{
NSApp.presentError(error)
Swift.print ("Error storing bookmark: \(url)")
return false
}
}
which throws an error in attempt to use the resolved URL to as the relative URL; I had originally just swapped the passed in URL to the origURL which ddin't work either.
The only solution is to not do this, or to previously be passed in the original URL. It's almost as if you cannot swap URLs you must be supplied that from either an open dialog or a pasteboard drop.
Are URLs which are aliases not suitable for sandbox work ?

How do I use `SecTrustSettingsSetTrustSettings` in swift?

I am working on certificate verification code for a NSURLSession in Swift. I would like to automatically adjust the trust settings for our own certificate which is used by a server.
The relevant code section is shown below:
SecCertificateAddToKeychain(firstCertificate, nil)
let sslTrust: [NSObject: AnyObject] = [kSecTrustSettingsPolicy: SecPolicyCreateSSL(true, nil)]
let result = SecTrustSettingsSetTrustSettings(firstCertificate, .User, sslTrust)
if (result == errSecSuccess) {
// success
} else {
print("Could not set trust settings \(result)")
}
I always get the result -50, which is errSecParam stating "One or more parameters passed to the function were not valid.".
If I replace the dictionary with an empty array [], the function returns success, so the problem is this last parameter.
How do I correctly use this function in swift?