iOS Key Chain alternative without possibility to restore on another device - ios

Is it possible to store some information on iOS that will not be deleted when the app is deleted (like Keychain) and also cannot be restored to another device?
As i understand - Key Chain will be restored to another device if you select an encrypted backup option.
Does iOS keychain storage persist when restoring an app to a new device?
So is it somehow possible to preserve some data - after the app is deleted (to read it after the reinstall) and for it only be avalible on the device it was added / created.

There are these options:
kSecAttrAccessibleAlwaysThisDeviceOnly
kSecAttrAccessibleWhenUnlockedThisDeviceOnly
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
From the docs:
[...] Items with this attribute do not migrate to a new device. [...]
I think they are exactly what you need. They are explained well in this WWDC talk:
https://developer.apple.com/videos/wwdc/2014/#711
Example usage from the above talk:
SecAccessControlRef sacObject =
SecAccessControlCreateWithFlags(kCFAllocatorDefault,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
kSecAccessControlUserPresence, &error);
NSData* secret = [#"top secret" dataWithEncoding:NSUTF8StringEncoding];
NSDictionary *query = #{
(id)kSecClass: (id)kSecClassGenericPassword,
(id)kSecAttrService: #"myservice",
(id)kSecAttrAccount: #"account name here",
(id)kSecValueData: secret};
OSStatus status = SecItemAdd((CFDictionaryRef)query, nil);
See also: https://developer.apple.com/library/ios/documentation/Security/Reference/keychainservices/index.html#//apple_ref/doc/constant_group/Keychain_Item_Accessibility_Constants

Related

Which unique device identifier do we have on iOS that does not change when an application is installed / removed / re-installed

I want to be collecting both the installation event (on the new app) and the account creation event (on all the old apps).
To ensure uniqueness and prevent fraud (that is each installation should correspond to a unique device, and should remain the same event if the app is un-installed and re-installed again on the same device) we can use hardware identifiers that can survive uninstall.
On android phone IMEI can be used as unique identifier, but this solution is not repeatable on iOS as Apple does not give access to any hardware or unique identifier for privacy reason.
I tried the approaches proposed in the following links:
link1
link2
link3
From link 1 & 3 I tried:
let deviceID = UIDevice.current.identifierForVendor!.uuidString
But this does not provide a unique ID that will remain the same once the app is un-installed and re-installed again.
Please is there a better approach for me to handle this issue in swift. Thank you!
You can use Device Check API introduced in iOS 11, if you want to identify user's device even if user reinstalls your app.
Your server can use the generated token on the apple device. for more details please refer the following documentation
https://developer.apple.com/documentation/devicecheck/dcdevice/2902276-generatetoken
After UUID deprication there is no way 100% accomplish that , you can store an identifier in keychain but starting from iOS 10.3 , when you delete the app , all associated keychain items will be deleted
Currently there is no way(Before iOS 5 it was) to get static UDID or any device related ID. I also gone through this problem and I did followings to achieve.
Create own random number
- (NSMutableString*)getSecureGeneratedNumber
{
uint8_t randomBytes[16];
int randomNumber = SecRandomCopyBytes(kSecRandomDefault, 16, randomBytes);
NSMutableString *uuidStringReplacement;
if(randomNumber == 0) {
uuidStringReplacement = [[NSMutableString alloc] initWithCapacity:16*2];
for(NSInteger index = 0; index < 16; index++)
{
[uuidStringReplacement appendFormat: #"%02x", randomBytes[index]];
}
NSLog(#"uuidStringReplacement is %#", uuidStringReplacement);
} else {
return 0;
NSLog(#"SecRandomCopyBytes failed for some reason");
}
return uuidStringReplacement;
}
Save this random number in User Default or Keychain
[[NSUserDefaults standardUserDefaults] setObject:randomNumber forKey:#"randomNum"];
[[NSUserDefaults standardUserDefaults]synchronize];

adding prefix to your current bundle id resets the keychain?

I have an app(app1) which is in appstore with bundle id com.x.y
Now i was developing another app(app2) under same developer account with bundle id com.x.z
I wanted to make the keychain value stored in app1 available to app2.
This availablity of keychain is determined by keychain-access-groups.
so if i add prefix( current Team id) to both the bundle id i am able to get the values. example teamid.com.x.y teamid.com.x.z
Issue is When i add the prefix to app1 which is in appstore, it ask for login credentials again which i dont want as app has lots of users.
I wasn't using prefix earlier i just added them.
Is there a way i could get the keychain access for both the apps without having user to login again.
First, it's important to realize that Xcode is already adding the AppIdentifierPrefix to your identifier. It unfortunately hides it in the GUI, but if you open the entitlements plist, you'll see it. This is the identifier that is used to sign the app, and it's the piece that is used to enforce access control. I don't believe the teamid prefix you're adding really does anything. I generally would recommend an access group com.x.shared or com.x.appgroup.shared and not use com.x.z (I'm assuming com.x.y already exists, so you can't change that).
I'm assuming here that you don't want to have to force users to upgrade App1, correct? I'm moving forward on that assumption.
If you can upgrade App1 (not require an upgrade, but make sure that all new customers have an upgraded version), then only store in com.x.y if it exists. Otherwise, store in com.x.shared:
When you read from the keychain, don't use an access group. This will get the first matching record.
When you write to the keychain, use the access group that was in the record you read.
If you don't want to upgrade App1 at all right now (required or not), then just always read and write to com.x.y in App2.
When you're ready to end-of-life the com.x.y group (if you're able to finally upgrade all App1 supported users), then you can switch to:
Read from com.x.y. If it's found, delete it, and recreate it as com.x.shared. You can do this one-time in the application startup (just write an NSUserDefaults that says you've done it.
From then on, always use com.x.shared explicitly.
The key tool here is that when you ask for an explicitly access group, you have to provide the whole thing, including you AppId (which isn't displayed in the Xcode GUI). You can of course hard-code it, but a better solution is to dynamically query it. I use an updated version of David H's code:
- (NSString *)bundleSeedID {
NSDictionary *query = #{ (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrAccount : #"bundleSeedIDQuery",
(__bridge id)kSecAttrService : #"",
(__bridge id)kSecReturnAttributes : (id)kCFBooleanTrue
};
CFDictionaryRef result = nil;
OSStatus status = SecItemCopyMatching((__bridge CFTypeRef)query,
(CFTypeRef *)&result);
if (status == errSecItemNotFound)
status = SecItemAdd((__bridge CFTypeRef)query, (CFTypeRef *)&result);
if (status != errSecSuccess)
return nil;
NSString *accessGroup = [(__bridge NSDictionary *)result
objectForKey:(__bridge id)kSecAttrAccessGroup];
NSArray *components = [accessGroup componentsSeparatedByString:#"."];
NSString *bundleSeedID = components[0];
CFRelease(result);
return bundleSeedID;
}
This will tell you your prefix at runtime. It does so b creating a bogus keychain entry, then querying it and seeing what access group was attached to it.
You may be interested in the first section of Getting Security and Privacy Right from Renaissance.io 2014. You can skip to "Protecting Secrets with Keychain."

SecItemCopyMatching with kSecMatchItemList fails

I'm having trouble getting this call to work for IOS. I've tried a number of approaches but nothing seems to work: I always get a status of errSecParam. Can anyone tell me what I'm doing wrong?
I started out using this to get a list of attributes for a certificate I hydrated from bytes. That didn't work so I reduced it to this and received the same error. I've tested on the simulator and an iPhone6 and get the same results.
First I get an array of certificates and then I pass the array back to SecItemCopyMatching. I've tried this where I queried for attributes only and get the same error.
I'm new to IOS so I don't doubt it's something I've missed.
Thanks.
// Call SecItemCopyMatching twice: the first time fetch an array of certificates
// and the second time use the array with kSecMatchItemList.
- (void) SecItemCopyMatchingTest2 {
// Now read them from the keychain.
NSMutableDictionary *query = [NSMutableDictionary dictionaryWithObjectsAndKeys:
(__bridge id)kSecClassCertificate,(__bridge id)kSecClass,
(__bridge id)kCFBooleanTrue, (__bridge id)kSecReturnRef,
(__bridge id)kSecMatchLimitAll, (__bridge id)kSecMatchLimit,
nil];
CFTypeRef result;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
NSArray *rgRefs = CFBridgingRelease(result);
if (status == noErr){
// this works
}
// Use the array we received from our previous call
[query setObject:rgRefs forKey:(__bridge id)kSecMatchItemList];
CFTypeRef result2;
// Results in status = errSecParam
status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result2);
if (status == errSecParam){
// the cal fails.
}
}
Quote from the header file documentation:
#constant kSecMatchItemList OS X only. Specifies a dictionary key whose value is a CFArray of SecKeychainItemRef items. If provided, returned items will be limited to the subset which are contained in this list.
I know that the __OSX_AVAILABLE_STARTING claims it exists for iOS but that is a lie, on the mailing list an Apple developer claimed it has never been implemented for iOS.
Try setting the same array with kSecUseItemList. This wasn't implemented for iOS either at that time according to mailing list, but if Apple is going to implement either one for iOS (or has already done so), it's rather kSecUseItemList.
There are a lot of strange keychain API differences between macOS and iOS and some of these are only found in documentation or just in the header files. Here's another example from the SecItemDelete() delete documentation:
To delete an item identified by a transient reference, on iOS, specify kSecValueRef with a item reference. On OS X, give a kSecMatchItemList containing an item reference.
Trying to use kSecMatchItemList for that on iOS will fail, just like using kSecValueRef on macOS seems to fail. These API differences don't make any sense to me, but either you can do it with kSecUseItemList or you are out of luck and it's simply not possible on iOS.

Keychain - Secure Data Storage

I am developing an application with keychain implementation . i am able to create & Save data into keychain . I am using the Keychain Wrapper classes provided By Apple.
According to requirement , I have to implement best possible Security in the KeyChain (The security team pointed out lapses , such as it's accessibility on Jail-broken devices).
Could Someone give me direction?
I had also Implemented keychain in application long Back using the same Wrapper you cited , but , of course with a lot of modifications.
Basically Keychain is quite secure .According to Apple , it's an encrypted container that holds secure information for multiple applications ,which means that when the keychain is locked, no one can access its protected contents .
In iOS , only the application creating the keychain can access it.
According to Apple's documentation , iOS can choose to Memory-Cache or Disk Cache it.
But from iOS 4.xx++ , it's only disk-cached(dunno why) , thus always creating a
sqlite DB , where all the data in the keychain are stored corresponding to a particular Identifier.
The Sqlite DB Can be Hacked on rooted or Jail-broken devices.
To Secure the Keychain
1 Add the security keyword "kSecAttrAccessibleWhenUnlockedThisDeviceOnly" while adding or
updating the data in keychain on the methods "SecItemUpdate" & "SecItemAdd".
Something like :-
- (void)writeToKeychain
{
NSDictionary *attributes = NULL;
NSMutableDictionary *updateItem = NULL;
OSStatus result;
if (SecItemCopyMatching((CFDictionaryRef)genericPasswordQuery, (CFTypeRef *)&attributes) == noErr)
{
updateItem = [NSMutableDictionary dictionaryWithDictionary:attributes];
[updateItem setObject:[genericPasswordQuery objectForKey:(id)kSecClass] forKey:(id)kSecClass];
NSMutableDictionary *tempCheck = [self dictionaryToSecItemFormat:keychainItemData];
[tempCheck removeObjectForKey:(id)kSecClass];
#if TARGET_IPHONE_SIMULATOR
[tempCheck removeObjectForKey:(id)kSecAttrAccessGroup];
#endif
[updateItem setObject:(id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly forKey:(id)kSecAttrAccessible];
result = SecItemUpdate((CFDictionaryRef)updateItem, (CFDictionaryRef)tempCheck);
NSAssert( result == noErr, #"Couldn't update the Keychain Item." );
CFRelease(attributes);
}
else
{
[keychainItemData setObject:(id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly forKey:(id)kSecAttrAccessible];
result = SecItemAdd((CFDictionaryRef)[self dictionaryToSecItemFormat:keychainItemData], NULL);
NSAssert( result == noErr, #"Couldn't add the Keychain Item." );
}
}
2 Encrypt the data before Adding to the Keychain .I used AES-128 Encryption.
Also ensure that the key used for Encryption is RSA key.(sent by SSL Web Service ).
NOTE :-The Keychain Data is stored in the /private/var/Keychains/keychain-2.db file on the iPhone.
Hope it helps you.
[attributeDict setObject:(__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];

How to update an already added SecIdentityRef to iOS app's keychain?

I have a PKCS12 file in my app's document folder that contains one certificate and one private key.
I am able to open this .p12 file, extract the identity object and display some information thanks to Apple's documentation (https://developer.apple.com/library/ios/#documentation/Security/Conceptual/CertKeyTrustProgGuide/iPhone_Tasks/iPhone_Tasks.html#//apple_ref/doc/uid/TP40001358-CH208-DontLinkElementID_10)
What I'm trying to do now is to store this Identity to the keychain so I can use it later. I've read a lot of different stuff on iOS Keychain and I'm having a hard time figuring out how it really works.
Apple's code seems to use a persistent_ref to retrieve the Identity stored in Keychain. But I don't really understand what this is... Is it a simple reference like a memory ref ? If that is the case what happens when the device reboots ?
Unable to find a lot more information about this I tried to do it differently by using the kSecAttr attributes.
The current code works fine to add Identity to keychain :
NSMutableDictionary * dictionary = [[[NSMutableDictionary alloc] init] autorelease];
[dictionary setObject:#"LABEL" forKey:kSecAttrLabel];
[dictionary setObject:(id)newIdentity forKey:(id)kSecValueRef];
OSStatus status = SecItemAdd((CFDictionaryRef)dictionary, NULL);
But if I try to add it a second time I'm receiving a -25299 error which is "fine" since it already exists. I tried handling it by an update like this :
NSMutableDictionary *searchDictionary = [[[NSMutableDictionary alloc] init] autorelease];
[searchDictionary setObject:#"LABEL" forKey:kSecAttrLabel];
[searchDictionary setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
[searchDictionary setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnRef];
NSMutableDictionary *updateDictionary = [[NSMutableDictionary alloc] init];
[updateDictionary setObject:(id)newIdentity forKey:(id)kSecValueRef];
OSStatus status = SecItemUpdate((CFDictionaryRef)searchDictionary,(CFDictionaryRef)updateDictionary);
With this code I get a -50 status error apparently because I have invalid parameters... Which one ? Why ? What can I do to update my keychain correctly ?
EDIT: As suggested I tried to delete the existing element prior to adding it but I'm stuck with the same status code (-50). Below is the code I tried:
NSMutableDictionary *searchDictionary = [self setupSearchDirectoryForIdentifier:identifier];
OSStatus status = SecItemDelete((CFDictionaryRef)searchDictionary);
NSAssert(status == noErr, #"Problem deleting current keychain item." );
setupSearchDirectoryForIdentifier simply create a NSDictionnary with the label of my item:
- (NSMutableDictionary *)setupSearchDirectoryForIdentifier:(NSString *)identifier {
// Setup dictionary to access keychain.
NSMutableDictionary *searchDictionary = [[[NSMutableDictionary alloc] init] autorelease];
[searchDictionary setObject:identifier forKey:kSecAttrLabel];
return searchDictionary;
}
Thank you
PS : I'm developing on Xcode 4.2 / iPad 5.1.1
Even if this is an old post, as I came up with similar issues very recently when working with identities in keychains (to be used for SSL client authentication), I am providing my own experience (with XCode 5.0.1) as support for this seems to be still very confusing.
SecItemAdd must indeed be used with very few keys. kSecValueRef (with the identity) is of course mandatory and optionally kSecAttrLabel. Using several other keys (e.g. kSecClass) resulted in silent failures, i.e. no error reported but the identity wasn't added. This was very confusing.
I didn't succeed either to use SecItemUpdate with such an identity. All my attempts resulted in a -50 error.
To be able to update an existing identity I deleted the existing identity using SecItemDelete and then added it with SecItemAdd. What was again confusing is that although the call to SecItemDelete returned a -25300 error back (errSecItemNotFound), it could be ignored as the identity was indeed deleted as it was possible to add it afterwards (using SecItemAdd) without an error.
SecIdentityRefs, SecKeyRefs and similar Sec...Ref values are ephemeral representations of keychain items. They become invalid when the application exits or when their retain count reaches zero. They cannot be directly saved in persistent storage.
On the other hand, a persistent reference is a piece of CFDataRef that you can use to retrieve a particular keychain item later. You can store it in a file, in NSUserDefaults, or anywhere else you fancy. It will not become invalid when the application exits or when the device is rebooted. (However, a persistent reference may become invalid when the keychain itself is removed (i.e., when device is restored), when the item it refers to is deleted or when one of the item's identifying attributes is modified.)
Apple's sample code uses SecItemAdd's second argument to retrieve persistent references to the items it adds to the keychain. This is then presumably stored in NSUserDefaults. Given the persistent reference, the app can later use SecItemCopyMatching to convert it to an SecIdentityRef that it can use.
Note that you don't have to use persistent references if you don't want to; if you wish, you can also choose to retrieve keychain items based on their label, or any other identifying attribute.
SecItemUpdate probably fails because a SecIdentityRef isn't a real keychain item. It is but a pseudo-item that is constructed when a public key and its associated private key are both on the keychain. As such, it doesn't make much sense to update it -- it doesn't own any attributes. All its properties are inherited from its associated SecCertificateRef (the certificate) and SecKeyRef (the private key). Updating these items instead of the identity should work. (But it is easier to simply create the item correctly: you can initialize any attribute by adding it and its value to SecItemAdd's first parameter.) Alternatively, you could try simply deleting the identity (SecItemDelete) before re-adding it to the keychain.

Resources