Device UUID not unique when user uninstall and re-install - ios

I have a system where each user allowed to install apps for two devices only. The issue is happened when user uninstall and reinstall again on the same device. So it will generate new UUID and when the apps check to web service.
The apps will send UUID and login id in order to check if the user with that login id has installed in more than two devices. I am using real iPhone and iPad device, not using simulator. I am not sure for production environment. Currently the apps is distributed using Apple TestFlight using AppStore Distribution profile.
I generate the uuid using this
let uuid = UIDevice.currentDevice().identifierForVendor!.UUIDString
Thanks.

You need to use keychain: (to be able to imort JNKeychain you need to enter a new string in your pod file pod 'JNKeychain'). This will guaranty you that if you don't change you bundle identifier, you will always have a uniq device id (that will stay the same even after deleting your app). I used that when user was banned forever in our application, he couldn't enter the app even with different account even after deleting the app.
import UIKit
import JNKeychain
class KeychainManager: NSObject {
static let sharedInstance = KeychainManager()
func getDeviceIdentifierFromKeychain() -> String {
// try to get value from keychain
var deviceUDID = self.keychain_valueForKey("keychainDeviceUDID") as? String
if deviceUDID == nil {
deviceUDID = UIDevice.current.identifierForVendor!.uuidString
// save new value in keychain
self.keychain_setObject(deviceUDID! as AnyObject, forKey: "keychainDeviceUDID")
}
return deviceUDID!
}
// MARK: - Keychain
func keychain_setObject(_ object: AnyObject, forKey: String) {
let result = JNKeychain.saveValue(object, forKey: forKey)
if !result {
print("keychain saving: smth went wrong")
}
}
func keychain_deleteObjectForKey(_ key: String) -> Bool {
let result = JNKeychain.deleteValue(forKey: key)
return result
}
func keychain_valueForKey(_ key: String) -> AnyObject? {
let value = JNKeychain.loadValue(forKey: key)
return value as AnyObject?
}
}

This is the expected bevaiour. If you want to use same UUID you need to save it to the keyChain. I have done something simmiler in of my apps using KeyChainWrapper
So here is a sample chunk for you
let deviceId = UIDevice.currentDevice().identifierForVendor?.UUIDString ?? ""
// Saving Id in keyChain
KeychainWrapper.defaultKeychainWrapper().setString(deviceId, forKey: "CurrentDeviceId")
And then just get Id from keyChain everytime you want to use it.
let previousDeviceId = KeychainWrapper.defaultKeychainWrapper().stringForKey("CurrentDeviceId")

Related

How to securely implement biometry with fallback application password to authenticate signing within Secure Enclave?

I am looking to implement a local authentication flow similar to many banking apps in an iOS (Swift) app for using a key in the Secure Enclave:
By default you set up an app-specific pin code
The user is then able to turn on biometry (Face ID or Touch ID) for quick authentication
The user can then sign a message using their private key stored in the Secure Enclave by either using biometrics or the user can fall back to their app-specific pin (either by choice, or if they cancel or can't be recognized for example).
What I've tried so far
The .applicationPassword flag for SecAccessControlCreateFlags seemed like a reasonable option to allow an application-defined password to be used. Furthermore, the set of possible flags also contain constraints such as such as devicePasscode and biometryCurrentSet to set access constraints that can even be combined by using conjunctions. It should be noted that applicationPassword is not a constraint but an 'option' according to the docs. It's also not very well-documented what this flag actually does. Still, I tried the following:
let flags1: SecAccessControlCreateFlags = [.privateKeyUsage,
.biometryCurrentSet, .or, .applicationPassword]
let flags2: SecAccessControlCreateFlags = [.privateKeyUsage,
.biometryCurrentSet, .applicationPassword]
let access1 = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
flags1,
&error)
let access2 = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
flags2,
&error)
Both of these options seem to work essentially the same, where first the phone prompts for biometric verification and then shows a prompt where you can enter the application password.
I tried programmatically supplying the application password using LAContext.setCredential, and this works fine as the application password prompt will no longer be shown, but iOS will still always prompt for biometrics as well (even when using the .or flag). Thus, it seems that the .or flag is not working as I had hoped together with .applicationPassword. However, these access control policies do seem to enforce that both biometrics and application password should pass, which is a nice possibility but not exactly what I was looking for.
I have also tried preventing the respective prompts such as the Face ID prompt from being shown with LAContext.interactionNotAllowed but this also does not work because of the access control flags.
Basic setup
For testing this, I have a simple iOS app set up with the following functions derived from the Secure Enclave documentation:
func getAccessControl() -> SecAccessControl {
var access: SecAccessControl?
access = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
[.privateKeyUsage,
.biometryCurrentSet,
.or,
.applicationPassword],
nil)!
return access
}
func generatePrivateKey() throws -> SecKey {
let context = LAContext()
context.setCredential("pwd123".data(using: .utf8), type: .applicationPassword)
let attributes: NSDictionary = [
kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeySizeInBits: 256,
kSecAttrTokenID: kSecAttrTokenIDSecureEnclave,
kSecUseAuthenticationContext as String: context,
kSecAttrLabel: "label-for-reference",
kSecClass: kSecClassKey,
kSecPrivateKeyAttrs: [
kSecAttrIsPermanent: true,
kSecAttrApplicationTag: "tag-for-reference",
kSecAttrAccessControl: getAccessControl(),
]
]
var error: Unmanaged<CFError>?
guard let privateKey = SecKeyCreateRandomKey(attributes, &error) else {
throw error!.takeRetainedValue() as Error
}
return privateKey
}
func retrieveKey(context: LAContext? = nil) -> SecKey? {
var attributes: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrLabel as String: "label-for-reference",
kSecMatchLimit as String: kSecMatchLimitOne,
kSecReturnRef as String: true,
]
if let context = context {
attributes[kSecUseAuthenticationContext as String] = context
}
var item: CFTypeRef?
let res = SecItemCopyMatching(attributes as CFDictionary, &item)
if (res == errSecSuccess) {
return (item as! SecKey)
} else {
return nil
}
}
func sign(data: String, key: SecKey) throws -> Data? {
if (SecKeyIsAlgorithmSupported(key, .sign, .ecdsaSignatureMessageX962SHA256)) {
var error: Unmanaged<CFError>?
guard let signature = SecKeyCreateSignature(key,
.ecdsaSignatureMessageX962SHA256,
data.data(using: .utf8)! as CFData,
&error) as Data? else {
throw error!.takeRetainedValue() as Error
}
return signature
}
return nil
}
I can then generate a signature by doing something like this (leaving out some details):
let privateKey = generatePrivateKey()
let context = LAContext()
context.setCredential("pwd123".data(using: .utf8), type: .applicationPassword)
let retrievedKey = retrieveKey(context)
var signedMessage: Data?
try signedMessage = sign(data: "abc", key: retrievedKey!))
What I'd like to learn
I'd like to learn if I'm missing something in this documentation (specifically on the access control of [secure enclave] keychain items), or alternatively, if there is a particular workaround to make this work. For example, I've also thought about chaining the access options, e.g. Face ID --unlocks--> Application passcode --unlocks--> Private key so that the application passcode can either be provided by using Face ID first or directly by the user. However, this would load the application passcode in memory (which may not be an issue when the user already enters their pin code in a custom user interface anyway). How do apps with similar functionality generally solve this?

ios kSecClassCertificate yields -25303

I'm trying to store p12(pfx) certificate into keychain on ios
with code from keychainswift essentially, just the klass changed from password to certificate:
#discardableResult
open func setCertificate(_ value: Data, forKey key: String,
withAccess access: KeychainSwiftAccessOptions? = nil) -> Bool {
// The lock prevents the code to be run simultaneously
// from multiple threads which may result in crashing
lock.lock()
defer { lock.unlock() }
deleteNoLock(key) // Delete any existing key before saving it
let accessible = access?.value ?? KeychainSwiftAccessOptions.defaultOption.value
let prefixedKey = keyWithPrefix(key)
var query: [String : Any] = [
KeychainSwiftConstants.klass : kSecClassCertificate,
KeychainSwiftConstants.attrAccount : prefixedKey,
KeychainSwiftConstants.valueData : value,
KeychainSwiftConstants.accessible : accessible
]
query = addAccessGroupWhenPresent(query)
query = addSynchronizableIfRequired(query, addingItems: true)
lastQueryParameters = query
lastResultCode = SecItemAdd(query as CFDictionary, nil)
return lastResultCode == noErr
}
getting -25303 (invalid attribute that is)
Should I piecemeal store separetely identity, certificate chain and trust with different keys for this to work?
what's the difference between kSecClassCertificate and kSecClassPassword klasses of storage given that we
have keychains sandboxes and without GUI?
is this for forward compatibility with macos or something?
PS. Apple please attempt to find a tech writer who can fix the horrendous documentation around security framework. Thanks!
All the available attributes for a certificate are mentioned in the documentation. I believe your attrAccount is an invalid parameter and I feel, but I'm not sure that the valueData might be invalid. So remove at least the attrAccount and possibly the valueData.

Keychain Query Always Returns errSecItemNotFound After Upgrading to iOS 13

I am storing passwords into the iOS keychain and later retrieving them to implement a "remember me" (auto-login) feature on my app.
I implemented my own wrapper around the Security.framework functions (SecItemCopyMatching(), etc.), and it was working like a charm up until iOS 12.
Now I am testing that my app doesn't break with the upcoming iOS 13, and lo and behold:
SecItemCopyMatching() always returns .errSecItemNotFound
...even though I have previously stored the data I am querying.
My wrapper is a class with static properties to conveniently provide the values of the kSecAttrService and kSecAttrAccount when assembling the query dictionaries:
class LocalCredentialStore {
private static let serviceName: String = {
guard let name = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String else {
return "Unknown App"
}
return name
}()
private static let accountName = "Login Password"
// ...
I am inserting the password into the keychain with code like the following:
/*
- NOTE: protectWithPasscode is currently always FALSE, so the password
can later be retrieved programmatically, i.e. without user interaction.
*/
static func storePassword(_ password: String, protectWithPasscode: Bool, completion: (() -> Void)? = nil, failure: ((Error) -> Void)? = nil) {
// Encode payload:
guard let dataToStore = password.data(using: .utf8) else {
failure?(NSError(localizedDescription: ""))
return
}
// DELETE any previous entry:
self.deleteStoredPassword()
// INSERT new value:
let protection: CFTypeRef = protectWithPasscode ? kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly : kSecAttrAccessibleWhenUnlocked
let flags: SecAccessControlCreateFlags = protectWithPasscode ? .userPresence : []
guard let accessControl = SecAccessControlCreateWithFlags(
kCFAllocatorDefault,
protection,
flags,
nil) else {
failure?(NSError(localizedDescription: ""))
return
}
let insertQuery: NSDictionary = [
kSecClass: kSecClassGenericPassword,
kSecAttrAccessControl: accessControl,
kSecValueData: dataToStore,
kSecUseAuthenticationUI: kSecUseAuthenticationUIAllow,
kSecAttrService: serviceName, // These two values identify the entry;
kSecAttrAccount: accountName // together they become the primary key in the Database.
]
let resultCode = SecItemAdd(insertQuery as CFDictionary, nil)
guard resultCode == errSecSuccess else {
failure?(NSError(localizedDescription: ""))
return
}
completion?()
}
...and later, I am retrieving the password with:
static func loadPassword(completion: #escaping ((String?) -> Void)) {
// [1] Perform search on background thread:
DispatchQueue.global().async {
let selectQuery: NSDictionary = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: serviceName,
kSecAttrAccount: accountName,
kSecReturnData: true,
kSecUseOperationPrompt: "Please authenticate"
]
var extractedData: CFTypeRef?
let result = SecItemCopyMatching(selectQuery, &extractedData)
// [2] Rendez-vous with the caller on the main thread:
DispatchQueue.main.async {
switch result {
case errSecSuccess:
guard let data = extractedData as? Data, let password = String(data: data, encoding: .utf8) else {
return completion(nil)
}
completion(password) // < SUCCESS
case errSecUserCanceled:
completion(nil)
case errSecAuthFailed:
completion(nil)
case errSecItemNotFound:
completion(nil)
default:
completion(nil)
}
}
}
}
(I don't think any of the entries of the dictionaries I use for either call has an inappropriate value... but perhaps I am missing something that just happened to "get a pass" until now)
I have set up a repository with a working project (Xcode 11 beta) that demonstrates the problem.
The password storing always succeeds; The password loading:
Succeeds on Xcode 10 - iOS 12 (and earlier), but
Fails with .errSecItemNotFound on Xcode 11 - iOS 13.
UPDATE: I can not reproduce the issue on the device, only Simulator. On the device, the stored password is retrieved successfully.
Perhaps this is a bug or limitation on the iOS 13 Simulator and/or iOS 13 SDK for the x86 platform.
UPDATE 2: If someone comes up with an alternative approach that somehow works around the issue (whether by design or by taking advantage of some oversight by Apple), I will accept it as an answer.
I've had a similar issue where I was getting errSecItemNotFound with any Keychain-related action but only on a simulator. On real device it was perfect, I've tested with latest Xcodes (beta, GM, stable) on different simulators and the ones that were giving me a hard time were iOS 13 ones.
The problem was that I was using kSecClassKey in query attribute kSecClass, but without the 'required' values (see what classes go with which values here) for generating a primary key:
kSecAttrApplicationLabel
kSecAttrApplicationTag
kSecAttrKeyType
kSecAttrKeySizeInBits
kSecAttrEffectiveKeySize
And what helped was to pick kSecClassGenericPassword for kSecClass and provide the 'required' values for generating a primary key:
kSecAttrAccount
kSecAttrService
See here on more about kSecClass types and what other attributes should go with them.
I came to this conclusion by starting a new iOS 13 project and copying over the Keychain wrapper that was used in our app, as expected that did not work so I've found this lovely guide on using keychain here and tried out their wrapper which no surprise worked, and then went line by line comparing my implementation with theirs.
This issue already reported in radar: http://openradar.appspot.com/7251207
Hope this helps.
After half a day of experimentation I discovered that using a pretty basic instance of kSecClassGenericPassword I had the problem on both the simulator and real hardware. After having a read over of the docs I noticed that kSecAttrSynchronizable has a kSecAttrSynchronizableAny. To accept any value for any other attribute, you simply don't include it in the query. That's a clue.
I found that when I included kSecAttrSynchronizable set to kSecAttrSynchronizableAny the queries all worked. Of course I could also set it to either kCFBooleanTrue (or *False) if I actually do want to filter on that value.
Given that attribute everything seems to work as expected for me. Hopefully this will save some other people a half day of mucking around with test code.
Update
Due to enhanced security requirements from above, I changed the access attribute from kSecAttrAccessibleWhenUnlocked to kSecAttrAccessibleWhenUnlockedThisDeviceOnly (i.e., prevent the password from being copied during device backups).
...And now my code is broken again! This isn't an issue of trying to read the password stored with the attribute set to kSecAttrAccessibleWhenUnlocked using a dictionary that contains kSecAttrAccessibleWhenUnlockedThisDeviceOnly instead, no; I deleted the app and started from scratch, and it still fails.
I have posted a new question (with a link back to this one).
Original Answer:
Thanks to the suggestion by #Edvinas in his answer above, I was able to figure out what was wrong.
As he suggests, I downloaded the Keychain wrapper class used in this Github repository (Project 28), and replaced my code with calls to the main class, and lo and behold - it did work.
Next, I added console logs to compare the query dictionaries used in the Keychain wrapper for storing/retrieving the password (i.e., the arguments to SecItemAdd() and SecItemCopyMatching) against the ones I was using. There were several differences:
The wrapper uses Swift Dictionary ([String, Any]), and my code uses NSDictionary (I must update this. It's 2019 already!).
The wrapper uses the bundle identifier for the value of kSecAttrService, I was using CFBundleName. This shouldn't be an issue, but my bundle name contains Japanese characters...
The wrapper uses CFBoolean values for kSecReturnData, I was using Swift booleans.
The wrapper uses kSecAttrGeneric in addition to kSecAttrAccount and kSecAttrService, my code only uses the latter two.
The wrapper encodes the values of kSecAttrGeneric and kSecAttrAccount as Data, my code was storing the values directly as String.
My insert dictionary uses kSecAttrAccessControl and kSecUseAuthenticationUI, the wrapper doesn't (it uses kSecAttrAccessible with configurable values. In my case, I believe kSecAttrAccessibleWhenUnlocked applies).
My retrieve dictionary uses kSecUseOperationPrompt, the wrapper doesn't
The wrapper specifies kSecMatchLimit to the value kSecMatchLimitOne, my code doesn't.
(Points 6 and 7 are not really necessary, because although I first designed my class with biometric authentication in mind, I am not using it currently.)
...etc.
I matched my dictionaries to those of the wrapper and finally got the copy query to succeed. Then, I removed the differing items until I could pinpoint the cause. It turns out that:
I don't need kSecAttrGeneric (just kSecAttrService and kSecAttrAccount, as mentioned in #Edvinas's answer).
I don't need to data-encode the value of kSecAttrAccount (it may be a good idea, but in my case, it would break previously stored data and complicate migration).
It turns out kSecMatchLimit isn't needed either (perhaps because my code results in a unique value stored/matched?), but I guess I will add it just to be safe (doesn't feel like it would break backward compatibility).
Swift booleans for e.g. kSecReturnData work fine. Assigning the integer 1 breaks it though (although that's how the value is logged on the console).
The (Japanese) bundle name as a value for kSecService is ok too.
...etc.
So in the end, I:
Removed kSecUseAuthenticationUI from the insert dictionary and replaced it with kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked.
Removed kSecUseAuthenticationUI from the insert dictionary.
Removed kSecUseOperationPrompt from the copy dictionary.
...and now my code works. I will have to test whether this load passwords stored using the old code on actual devices (otherwise, my users will lose their saved passwords on the next update).
So this is my final, working code:
import Foundation
import Security
/**
Provides keychain-based support for secure, local storage and retrieval of the
user's password.
*/
class LocalCredentialStore {
private static let serviceName: String = {
guard let name = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String else {
return "Unknown App"
}
return name
}()
private static let accountName = "Login Password"
/**
Returns `true` if successfully deleted, or no password was stored to begin
with; In case of anomalous result `false` is returned.
*/
#discardableResult static func deleteStoredPassword() -> Bool {
let deleteQuery: NSDictionary = [
kSecClass: kSecClassGenericPassword,
kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
kSecAttrService: serviceName,
kSecAttrAccount: accountName,
kSecReturnData: false
]
let result = SecItemDelete(deleteQuery as CFDictionary)
switch result {
case errSecSuccess, errSecItemNotFound:
return true
default:
return false
}
}
/**
If a password is already stored, it is silently overwritten.
*/
static func storePassword(_ password: String, protectWithPasscode: Bool, completion: (() -> Void)? = nil, failure: ((Error) -> Void)? = nil) {
// Encode payload:
guard let dataToStore = password.data(using: .utf8) else {
failure?(NSError(localizedDescription: ""))
return
}
// DELETE any previous entry:
self.deleteStoredPassword()
// INSERT new value:
let insertQuery: NSDictionary = [
kSecClass: kSecClassGenericPassword,
kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
kSecValueData: dataToStore,
kSecAttrService: serviceName, // These two values identify the entry;
kSecAttrAccount: accountName // together they become the primary key in the Database.
]
let resultCode = SecItemAdd(insertQuery as CFDictionary, nil)
guard resultCode == errSecSuccess else {
failure?(NSError(localizedDescription: ""))
return
}
completion?()
}
/**
If a password is stored and can be retrieved successfully, it is passed back as the argument of
`completion`; otherwise, `nil` is passed.
Completion handler is always executed on themain thread.
*/
static func loadPassword(completion: #escaping ((String?) -> Void)) {
// [1] Perform search on background thread:
DispatchQueue.global().async {
let selectQuery: NSDictionary = [
kSecClass: kSecClassGenericPassword,
kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
kSecAttrService: serviceName,
kSecAttrAccount: accountName,
kSecMatchLimit: kSecMatchLimitOne,
kSecReturnData: true
]
var extractedData: CFTypeRef?
let result = SecItemCopyMatching(selectQuery, &extractedData)
// [2] Rendez-vous with the caller on the main thread:
DispatchQueue.main.async {
switch result {
case errSecSuccess:
guard let data = extractedData as? Data, let password = String(data: data, encoding: .utf8) else {
return completion(nil)
}
completion(password)
case errSecUserCanceled:
completion(nil)
case errSecAuthFailed:
completion(nil)
case errSecItemNotFound:
completion(nil)
default:
completion(nil)
}
}
}
}
}
Final Words Of Wisdom: Unless you have a strong reason not to, just grab the Keychain Wrapper that #Edvinas mentioned in his answer (this repository, project 28)) and move on!
Regarding the issue in kSecClassGenericPassword, I was trying to understand what is the problem and I found a solution for that.
Basically it seems like Apple was fixing an issue with kSecAttrAccessControl, so below iOS version 13 you add keyChain object with kSecAttrAccessControl without biometric identity and above iOS 13 that does not work anymore in a simulator.
So the solution for that is when you want to encrypt the keyChain object with biometric you need to add kSecAttrAccessControl to your query but if you don't need to encrypted by biometric you need to add only kSecAttrAccessible that's the right way to do these.
Examples
Query for biometric encrypt:
guard let accessControl = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
kSecAttrAccessibleWhenUnlocked,
userPresence,
nil) else {
// failed to create accessControl
return
}
var attributes: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
kSecAttrService: "Your service",
kSecAttrAccount: "Your account",
kSecValueData: "data",
kSecAttrAccessControl: accessControl]
Query for regular KeyChain (without biometric):
var attributes: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
kSecAttrService: "Your service",
kSecAttrAccount: "Your account",
kSecValueData: "data",
kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly]
We had the same issue when generating a key pair - works just fine on devices, but on simulator iOS 13 and above it cannot find the key when we try to retreive it later on.
The solution is in Apple documentation: https://developer.apple.com/documentation/security/certificate_key_and_trust_services/keys/storing_keys_in_the_keychain
When you generate keys yourself, as described in Generating New
Cryptographic Keys, you can store them in the keychain as an implicit
part of that process. If you obtain a key by some other means, you can
still store it in the keychain.
In short, after you create a key with SecKeyCreateRandomKey, you need to save this key in the Keychain using SecItemAdd:
var error: Unmanaged<CFError>?
guard let key = SecKeyCreateRandomKey(createKeyQuery as CFDictionary, &error) else {
// An error occured.
return
}
let saveKeyQuery: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: tag,
kSecValueRef as String: key
]
let status = SecItemAdd(saveKeyQuery as CFDictionary, nil)
guard status == errSecSuccess else {
// An error occured.
return
}
// Success!

Realm Encryption, Secure Enclave, and Keychain

Sorry to be yet another post about encryption, but I am struggling with setting up some truly strong encryption in my application. I currently have a basic setup for login that utilizes keychain and is based off of Tim Mitra's tutorial, which works wonderfully. However, I am uncomfortable about storing the account email / username in UserDefaults as it isn't particularly secure. Is there a better method that anyone can come up with? Additionally, I am working on utilizing Realm's built in encryption features, however I am unsure as to how I should properly store the key for said encryption given that it is of type Data. I also have heard that I should double encrypt the user's credentials using Secure Enclave and possibly utilize the same technique with Realm's key. Is there a guide that someone could point me to? How would you better optimize my code to be brutally secure? I have already set the application to check the device for Cydia and other signs of jailbreaking so as to avoid keychain data dumps and plan on checking any / all urls called too.
Here's my implementation of Keychain:
private func setupAccount()
{
let newAccountName = inputFields[0].text
let newPassword = inputFields[1].text
let hasLoginKey = UserDefaults.standard.bool(forKey: "hasSetup")
if !hasLoginKey {
UserDefaults.standard.setValue(inputFields[0].text, forKey: "username")
}
do {
// This is a new account, create a new keychain item with the account name.
let passwordItem = KeychainLIPassItem(service: KeychainConfiguration.serviceName,
account: newAccountName!,
accessGroup: KeychainConfiguration.accessGroup)
// Save the password for the new item.
try passwordItem.savePassword(newPassword!)
} catch {
fatalError("Error updating keychain - \(error)")
}
UserDefaults.standard.set(true, forKey: "hasSetup")
}
Here is what I currently have for Realm Encryption:
private func keyValue() -> Data
{
var key = Data(count: 64)
_ = key.withUnsafeMutableBytes { bytes in
SecRandomCopyBytes(kSecRandomDefault, 64, bytes)
}
return key
}
private func securitySettings() -> Realm.Configuration
{
let key = keyValue()
let config = Realm.Configuration(encryptionKey: key)
return config
}
private func setupObject()
{
do {
let realm = try Realm(configuration: securitySettings())
let profile = UserProfile()
profile.firstName = firstName
profile.lastName = lastName
profile.dateOfBirth = dateOfBirth
profile.gender = gender
try! realm.write {
realm.add(profile)
}
} catch let error as NSError {
fatalError("Error opening realm: \(error)")
}
}
Thank you so much!

Access to shared keychain in iOS 8 share extension returns nil

The problem:
I have an iOS 8 application with a framework for keychain access and a share extension.
Both, the host application and the share extension link the keychain access framework and they both share a keychain.
Everything works in Debug configuration. But when I use the Release configuration the value dataTypeRef is always nil when I try to receive a password from the keychain.
I have already tried to add an App Group but without luck.
Has anyone an idea why it doesn't work for Release builds? Thanks
The code for the keychain access:
In the keychain access framework I add a password to the keychain like this:
public class func setPassword(password: String, account: String, service: String = "kDDHDefaultService") {
var secret: NSData = password.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
let objects: Array = [secClassGenericPassword(), service, account, secret]
let keys: Array = [secClass(), secAttrService(), secAttrAccount(), secValueData()]
let query = NSDictionary(objects: objects, forKeys: keys)
SecItemDelete(query as CFDictionaryRef)
let status = SecItemAdd(query as CFDictionaryRef, nil)
}
To receive the password I do this:
public class func passwordForAccount(account: String, service: String = "kDDHDefaultService") -> String? {
let keys: [AnyObject] = [secClass(), secAttrService(), secAttrAccount(), secReturnData()]
let objects: [AnyObject] = [secClassGenericPassword(), service, account, true]
println("keys \(keys), objects \(objects)")
let queryAttributes = NSDictionary(objects: objects, forKeys: keys)
var dataTypeRef : Unmanaged<AnyObject>?
let status = SecItemCopyMatching(queryAttributes, &dataTypeRef);
if status == errSecItemNotFound {
println("not Found")
return nil
}
if dataTypeRef == nil { // dataTypeRef is always nil in Release builds
println("dataTypeRef == nil")
return nil
}
let retrievedDataRef : CFDataRef = dataTypeRef!.takeRetainedValue() as CFDataRef
let retrievedData : NSData = retrievedDataRef
let password = NSString(data: retrievedData, encoding: NSUTF8StringEncoding)
return password as? String
}
Note: This has nothing to do with the share extension it even doesn't work for the host application.
Update: For a test I have added the KeychainAccess class to the target of the host application. But I still can't access the keychain in Release builds.
I think I found a temporary fix. As suggested in a comment to this answer I set the optimization of the Swift compiler to None[-Onone] in the keychain access target and it seems to work now.

Resources