Detect iCloud account availibility / change using Key-Value store only? - ios

I use iCloud Key-Value store, without any container (as I don't need documents).
So the app is not listed in Settings / iCloud / Storage / Manage Storage list, only in iCloud / iCloud Drive list, like this:
I really want to know somehow if it is turned ON or OFF, or available at all.
Seems ubiquityIdentityToken is always nil, and NSUbiquityIdentityDidChangeNotification never gets called.
I have a working prototype, modifiy a slider in simulator, it gets updated on the device, everything seems fine. Even though, if I ask for the identifiers (in the NSUbiquitousKeyValueStoreDidChangeExternallyNotification callback), they're always null:
NSURL *containerURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
id token = [[NSFileManager defaultManager] ubiquityIdentityToken];
NSLog(#"containerURL: %#", containerURL);
NSLog(#"token: %#", token);
// containerURL: (null)
// token: (null)
When I turn off iCloud Drive for my app, it gets terminated in the background. No any notification gets called, nor at relaunch.
Should I simply setup a Document container I never use?

Having iCloud Documents turned on, I have the token (app entitlements get some new values as well).
But the ubiquityIdentityToken now returns value even if I turn iCloud Drive OFF (!) in device settings for the given app. It does not pulls updates from the Key-Value store, still the token is set.
Only URLForUbiquityContainerIdentifier returns nil when turned OFF, and the URL when turned ON.

Related

How to test if Core Data SQLlite file is encrypted?

Core Data seems to be encrypted by default when device is locked but only before the first unlock.
From apple docs
For apps built for iOS 5.0 or later, persistent stores now store data
by default in an encrypted format on disk. The default protection
level prevents access to the data until after the user unlocks the
device for the first time.
So I set it up to get encrypted whenever the device is locked. The encryption settings for the SQLite file are set before returning the _persistentStoreCoordinator like so:
NSDictionary *fileAttributes = [NSDictionary dictionaryWithObject:NSFileProtectionComplete forKey:NSFileProtectionKey];
if (![[NSFileManager defaultManager] setAttributes:fileAttributes ofItemAtPath:storeURL.path error:&error]) {
//Handle error
}
return _persistentStoreCoordinator;
Code is from here.
I would like to test if the file is really encrypted.
What I did is lock the device and download the app container using Xcode->Window->Devices. However the file is not shown in the container. If I do the same when the device is unlocked I can then find it in the container. Why is that? More importantly can I test to see that the file gets encrypted when the phone is locked or it being missing is proof enough.
EDIT: A better setting up encryption for Core Data per this answer suggestion would be:
NSDictionary *storeOptions = #{NSPersistentStoreFileProtectionKey: NSFileProtectionComplete};
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:storeOptions error:&error]){
//handle error
}
What I did is lock the device and download the app container using
Xcode->Window->Devices. However the file is not shown in the
container. If I do the same when the device is unlocked I can then
find it in the container.
From what I understand this is how Apple encryption works. When your device is locked you don't see encrypted files in the device container, when it is open you do see them.

How can I use file encryption when calling parent application from Watch app?

I am calling a parent app on my iPhone from an Apple Watch app using openParentApplication and handleWatchKitExtensionRequest. In the main app, I use CoreData with the following options for addPersistentStoreWithType:
NSDictionary *options = #{
NSMigratePersistentStoresAutomaticallyOption : #YES, //
NSInferMappingModelAutomaticallyOption : #YES, //
NSSQLitePragmasOption : #{#"journal_mode" : #"DELETE"}, //
NSPersistentStoreFileProtectionKey : NSFileProtectionCompleteUnlessOpen
};
This caused an exception:
This NSPersistentStoreCoordinator has no persistent stores (device
locked). It cannot perform a save operation.
Does this mean that I can neither use NSFileProtectionCompleteUnlessOpen nor NSFileProtectionComplete?
Do I have to use NSFileProtectionNone or NSFileProtectionCompleteUntilFirstUserAuthentication?
I would like to know a way to protect my data by using NSFileProtectionCompleteUnlessOpen and still be able to access the data when my Watch app uses openParentApplication.
Possible ways to deal with the problem (but not a real solution)
Have two files (e.g., SQL data bases), where one is encrypted and the other one is not. The latter one would store only the data required by the Watch app.
NSFileProtectionCompleteUntilFirstUserAuthentication seems to be the recommended way for me. It makes sure the user has to unlock the device at least once since the last boot.
This problem was introduced with iOS 7 and background refresh. It's to prevent physical forensic analysis to read your unencrypted data.
Additionaly information from https://security.stackexchange.com/questions/57588/iphone-ios-7-encryption-at-lock-screen:
NSFileProtectionNone: file can be accessed any time, even if device is locked;
NSFileProtectionComplete: file can accessed only when device is unlocked (note there's ~10 seconds grace period after device is locked during which files are still accessible);
NSFileProtectionCompleteUnlessOpen: file can be created while device is locked, but once closed, can only be accessed when device is unlocked;
NSFileProtectionCompleteUntilFirstUserAuthentication: file can be accessed only if device has been unlocked at least once since boot.
The guys from Gilt also explained a lot about this behaviour here: http://tech.gilt.com/post/67708037571/sleuthing-and-solving-the-user-logout-bug-on-ios
Another idea which just came into my mind is to use an app group container. See the question here: WatchKit SDK not retrieving data from NSUserDefaults This way it should not only share NSUserDefaults but also the same keychain. This should work the same way to iOS Apps share the same keychain.

How do I check whether I can access a file saved with NSFileProtectionKey = NSFileProtectionCompleteUntilFirstUserAuthentication

I would like the determine if I have access to files that have been saved with the attribute NSFileProtectionKey = NSFileProtectionCompleteUntilFirstUserAuthentication.
I have tried [UIApplication sharedApplication].protectedDataAvailable, however from my tests it will return NO whenever the device is locked (if a pin code is set) even if the user has unlocked the device at least once since last starting the phone.
It says quite clearly in the docs: The value of this property is NO if data protection is enabled and the device is currently locked, and in this case files that were assigned the NSFileProtectionComplete or NSFileProtectionCompleteUnlessOpen protection key cannot be read or written by your app. i.e. this is not the correct property.
You need use one of the multi-tasking keys that causes the launch the app on boot - e.g. the voip key for UIBackgroundModes.
Mind you, you're pretty much testing the OS at that point - if you set the keys appropriately when creating the files it should work as advertised. If it doesn't then log a radar.

Is it possible to intercept iCloud switching on/off in Settings -> iCloud -> Document & Data?

Is it possible to intercept if the user switches from on to off the iCloud support under Settings -> iCloud -> Document & Data?
Obviously when he does this the app has already resigned active and entered the background. I am targeting iOS7 and I would like to keep in sync the UIManagedDocument, otherwise it would be like having two different UIDocuments: one with iCloud support and all the data created until the switch from on to off and a new one without any data in it. If I create data when iCloud support has been switched to off, and then I switch back to on I get the same DB I had when the support has been switched to off.
Note: I believe nelico's answer is right. He wrote: "If your app is running and the user changes enables or disable Document & Data iCloud syncing via the settings app, your app will receive the SIGKILL signal."
When the user changes the settings the app is ALREADY in the background and receives the SIGKILL signal. This is what I do not understand and I do not want. Registering for NSUbiquityIdentityDidChangeNotification doesn't solve this problem.
Another, cleaner, solution is to listen for the NSUbiquityIdentityDidChangeNotification notification and when you get that notification then check the URLForUbiquityContainerIdentifier if it is null they either signed out or turned off 'Documents and Data'. You should also be tracking the current ubiquity token so that you can know not only if they logged off but if they changed iCloud accounts. It happens more than one might think because Apple Geniuses like to just create a new iCloud account when things go wrong on users devices.
ex:
id <NSObject,NSCopying,NSCoding> _currentUbiquityIdentityToken;
...
_currentUbiquityIdentityToken = [[NSFileManager defaultManager] ubiquityIdentityToken];
[[NSNotificationCenter defaultCenter] addObserver: self selector: #selector (_iCloudAccountAvailabilityChanged:) name: NSUbiquityIdentityDidChangeNotification object: nil];
...
- (void)_iCloudAccountAvailabilityChanged:(NSNotification*)notif {
if (![_currentUbiquityIdentityToken isEqual:[[NSFileManager defaultManager] ubiquityIdentityToken]]) {
// Update the current token and rescan for documents.
_currentUbiquityIdentityToken = [[NSFileManager defaultManager] ubiquityIdentityToken];
// Do something about the change here...
}
}
Usually you would put the checks in the method below. That way whenever the app becomes active (including first time its launched) you can check the settings just prior to returning control to the user, no need to do polling in the background. For UIManagedDocument you may want to migrate the store to a local only copy and remove any iCloud content or the opposite, depending on the users input.
BTW just include the check in the previous answer to test if the global iCloud settings have been turned on or off. I don't do this because its only necessary to change the apps behaviour if the user has set the app specific settings.
/*! The app is about to enter foreground so use this opportunity to check if the user has changed any
settings. They may have changed the iCloud account, logged into or out of iCloud, set Documents & Data to off (same effect as
if they logged out of iCloud) or they may have changed the app specific settings.
If the settings have been changed then check if iCloud is being turned off and ask the user if they want to save the files locally.
Otherwise just copy the files to iCloud (don't ask the user again, they've just turned iCloud on, so they obviously mean it!)
#param application The application
*/
- (void)applicationWillEnterForeground:(UIApplication *)application
{
//LOG(#"applicationWillEnterForeground called");
// Check if the settings have changed
[[NSUserDefaults standardUserDefaults] synchronize];
NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
bool userICloudChoice = [userDefaults boolForKey:_cloudPreferenceKey];
// Check against the current in memory setting
if (userICloudChoice == useICloudStorage) {
// The setting has not been changed so just ignore
//LOG(#" iCloud choice has not changed");
} else {
// The setting has changed so do something
//LOG(#" iCloud choice has been changed!!");
// iCloud has been turned off so ask the user if they want to keep files locally
if (!userICloudChoice) {
//LOG(#" Ask user if they want to keep iCloud files ?");
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
_cloudChangedAlert = [[UIAlertView alloc] initWithTitle:#"You're not using iCloud" message:#"What would you like to do with documents currently on this phone?" delegate:self cancelButtonTitle:#"Keep using iCloud" otherButtonTitles:#"Keep on My iPhone", #"Delete from My iPhone", nil];
} else {
_cloudChangedAlert = [[UIAlertView alloc] initWithTitle:#"You're not using iCloud" message:#"What would you like to do with documents currently on this phone?" delegate:self cancelButtonTitle:#"Keep using iCloud" otherButtonTitles:#"Keep on My iPad", #"Delete from My iPad", nil];
}
[_cloudChangedAlert show];
} else {
// iCloud has been turned on so just copy the files across, don't ask the user again...
//LOG(#" iCloud turned on so copy any created files across");
[[CloudManager sharedManager] setIsCloudEnabled:YES]; // This does all the work based on the settings passed to it
useICloudStorage = YES;
}
}
}
Oh and also register for this notification in case the user logs on using another iCloud account.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(checkUserICloudPreferenceAndSetupIfNecessary) name:NSUbiquityIdentityDidChangeNotification object:nil];
It's not possible to intercept the changes, but you can programmatically check to see if they have iCloud enabled:
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *ubiquityContainerURL = [fileManager URLForUbiquityContainerIdentifier:nil];
if (!ubiquityContainerURL) {
// iCloud is not enabled
}
When your app enters the background, you could periodically poll this and have your app respond to any change in state.
EDIT: If your app is running and the user changes enables or disable Document & Data iCloud syncing via the settings app, your app will receive the SIGKILL signal. However, the OS can terminate your app for a number of reasons so trapping SIGKILL is not a reliable method of intercepting iCloud sync setting changes. You are still better off periodically polling.

FMDB and encryption

I'm using FMDB to work with sqlite and I'd prefer to avoid a dependency on SQLCipher. How can I simply leverage the DataProtection capability built into iOS? Is this possible - the only requirement is to protect the data in the event of the phone being stolen.
If the phone is unlocked with a PIN, it's fine that the user could access the DB - it's their data.
Look for the line where you do databaseWithPath: (or initWithPath:), then add:
FMDatabase *db = [FMDatabase databaseWithPath:path];
NSDictionary *attributes = #{NSFileProtectionKey: NSFileProtectionCompleteUnlessOpen};
NSError *error;
BOOL success = [[NSFileManager defaultManager] setAttributes:attributes
ofItemAtPath:path
error:&error];
if (!success) {
NSLog(#"File protection failed: %#", error);
}
The possible Values for the NSFileProtectionKey key are:
NSFileProtectionNone:
The file has no special protections associated with it. It can be read from or written to at any time.
NSFileProtectionComplete:
The file is stored in an encrypted format on disk and cannot be read from or written to while the device is locked or booting.
NSFileProtectionCompleteUnlessOpen:
The file is stored in an encrypted format on disk. Files can be created while the device is locked, but once closed, cannot be opened again until the device is unlocked. If the file is opened when unlocked, you may continue to access the file normally, even if the user locks the device. There is a small performance penalty when the file is created and opened, though not when being written to or read from. This can be mitigated by changing the file protection to NSFileProtectionComplete when the device is unlocked.
NSFileProtectionCompleteUntilFirstUserAuthentication:
The file is stored in an encrypted format on disk and cannot be accessed until after the device has booted. After the user unlocks the device for the first time, your app can access the file and continue to access it even if the user subsequently locks the device.
The right type of protection may depend on the version of iOS (the last two are not available on iOS 4) and whether you use your database when the device is locked.
By far the easiest way is to turn on Data Protection for the entire app. Go to App IDs, click "Edit" and set "Sharing and Permissions" to "Complete Protection."
Update Xcode with your new app id information, and from there on, it'll be handled for your app automatically.

Resources