Implementing Data Protection

I have a couple of questions about how to properly implement data protection for my iOS apps. I couldn't find very much information about how the AppID Entitlement and it's default then relates to the options that you set when you write a file using NSFileManager or NSData writetofile.


  1. If you don't have the data protection entitlement on for your AppID, does this mean that adding NSDataWritingFileProtectionComplete to your code will not provide any protection?
  2. If you do have the data protection entitlement enabled in your AppID, and you have set that entitlement to Protected Until First User Authentication, is this the default level of protection? Can you still choose a higher level of data protection like NSDataWritingFileProtectionComplete for particular files?


Thanks

1. If you don't have the data protection entitlement on for your AppID, does this mean that adding NSDataWritingFileProtectionComplete to your code will not provide any protection?

No. The entitlement sets the default value for your container, and hence for anything created within your container. You can always override that default programmatically (via

NSDataWritingFileProtectionComplete
or any of the other data protection APIs).

2. If you do have the data protection entitlement enabled in your AppID, and you have set that entitlement to Protected Until First User Authentication, is this the default level of protection?

Yes, but see below.

Can you still choose a higher level of data protection like NSDataWritingFileProtectionComplete for particular files?

Yes.

Be aware that the definition of default is more subtle than you might think. By default the data protection value is inherited from the parent directory when you create an item. For example, if you have a directory set to

NSFileProtectionComplete
, any items created within that directory will, by default, be set to
NSFileProtectionComplete
. The entitlement controls the data protection value for the root directory of your container, which is then inherited by anything created within that container. However, if you explicitly set the value for a directory then subsequent items created within that directory will get the new value by default.

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks Eskimo, that's great, very helpful

Hi,

sorry to jump into this old thread, but I've a question about the following sentence "By default the data protection value is inherited from the parent directory when you create an item." because as far as I have tested this, it seems to be a wrong assumption.

For example I have the following code:

Code Block objc
BOOL success = YES;
NSError *error = nil;
NSURL *tmpDir = [NSFileManager.defaultManager.temporaryDirectory URLByAppendingPathComponent:@"tmpDir"];
success = [NSFileManager.defaultManager createDirectoryAtURL:tmpDir withIntermediateDirectories:YES attributes:@{ NSFileProtectionKey: NSFileProtectionComplete } error:&error];
NSFileProtectionType tmpDirFileProtectionType = [NSFileManager.defaultManager attributesOfItemAtPath:tmpDir.path error:nil][NSFileProtectionKey];
// tmpDirFileProtectionType == NSFileProtectionComplete -> this is OK and expected
NSURL *subDir = [tmpDir URLByAppendingPathComponent:@"subDir"];
success = [NSFileManager.defaultManager createDirectoryAtURL:subDir withIntermediateDirectories:YES attributes:nil error:&error];
NSFileProtectionType subDirFileProtectionType = [NSFileManager.defaultManager attributesOfItemAtPath:subDir.path error:nil][NSFileProtectionKey];
// subDirFileProtectionType == NSFileProtectionComplete -> this is OK and expected
NSString *someStr = @"someStr";
NSURL *someStrFile1 = [tmpDir URLByAppendingPathComponent:@"someStr1.txt"];
NSURL *someStrFile2 = [subDir URLByAppendingPathComponent:@"someStr2.txt"];
success = [someStr writeToURL:someStrFile1 atomically:YES encoding:NSUTF8StringEncoding error:&error];
success = [someStr writeToURL:someStrFile2 atomically:YES encoding:NSUTF8StringEncoding error:&error];
NSFileProtectionType someStrFile1FileProtectionType = [NSFileManager.defaultManager attributesOfItemAtPath:someStrFile1.path error:nil][NSFileProtectionKey];
NSFileProtectionType someStrFile2FileProtectionType = [NSFileManager.defaultManager attributesOfItemAtPath:someStrFile2.path error:nil][NSFileProtectionKey];
// someStrFile1FileProtectionType == NSFileProtectionCompleteUntilFirstUserAuthentication -> this is NOT OK and NOT expected
// someStrFile2FileProtectionType == NSFileProtectionCompleteUntilFirstUserAuthentication -> this is NOT OK and NOT expected


I've tested this on an iPhone X with iOS 14.5 and the app does not use the file protection entitlement.

Am I doing something wrong?
What do you see if you pass NO to the atomically parameter?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
If I pass NO to the atomically parameter the file inherits the NSFileProtectionType.
Is it expected/documented that the atomically parameter prevents the inheritance of NSFileProtectionType?

Furthermore I have observed a similar behavior with -[NSString copyItemAtURL:toURL:error:]. The copied item does not inherit the NSFileProtectionType of its new parent directory. Do I have to manually change the attribute after the item has been copied?

Is it expected/documented that the atomically parameter prevents the
inheritance of NSFileProtectionType?

AFAIK it’s not documented and, as demonstrated by this thread, expectations depend on experience (-:

What’s happening here is:
  • Passing YES to atomically triggers a safe save.

  • The standard algorithm for a safe save is to save the data in a temporary file and then rename it to the new name, thus replacing the old file with the new file.

  • The temporary directory has default data protection, and so that’s what the temporary file gets.

  • Data protection is an attribute of the file, not the name, and thus the final file ends up with the temporary file’s data protection.

The easy fix here is to move over to -writeToURL:options:error: which takes a NSDataWritingOptions parameter which allows you to specify both NSDataWritingAtomic and various NSDataWritingFileProtectionXxx values.

The copied item does not inherit the NSFileProtectionType of its new
parent directory.

Oh boy, that’s a whole different kettle of fish, one that depends how you define what a file is. Is it just the bytes in the data fork? What about metadata, like the creation date? What about the extended attributes? Different people have different definitions and those differences are reflected in various APIs. Indeed, this even bubbles up to the user level, for example:
  • If you copy a file using the Finder it preserves the creation date.

  • If you copy a file using cp it does not.

Data protection is just one piece of metadata and it’s clear that FileManager has chosen to preserver it.

Do I have to manually change the attribute after the item has been
copied?

That’s less than ideal from a security perspective because there’s a window of time where it’s unprotected.

How big is this file? If it’s tiny you could just copying it by using NSData to read the old one and write the new one. NSData gives you control over data protection and thus there’s no window of vulnerability.

If the file is too large to fit in memory then I’d have to do some more research as to how best to copy it.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
Thanks a lot for your quick response and the detailed explanation!

The more I think about files and its metadata, the observed behavior makes sense to me :D

So for creating new files I will definitely change my code to use -writeToURL:options:error:.

How big is this file?

The files in my app are typically documents like TXT, PDF, MS Word, etc. Therefor the size varies between a few KBs and many MBs.
I guess storing such files in NSData would exceed the memory limits pretty fast.

Unless you want to spend more time with some research about this topic, I am going to use NSFileManagers -copyItemAtURL:toURL:error: in combination with -setAttributes:ofItemAtPath:error:.

I guess storing such files in NSData would exceed the memory limits
pretty fast.

Right. The “many MBs” case is a concern because it will radically increase your peak memory usage, which runs the risk of you being jetsammed.

The alternative here is to write your own copy engine. Whether that’s sensible depends on the nature of these items. If each item is a single file, and you don’t care about its metadata or EAs, then writing code to copy that is pretty simple. If you have to worry about hierarchies, metadata, and EAs, then things get very complex very fast.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

How do I 'migrate' the data protection level of UserDefaults files/storage?

AFAIK there’s no good way to do that. You can change the data protection on specific files and folders (using NSURLFileProtectionKey) but we don’t document the specific files and folders used by UserDefaults.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

👍

Implementing Data Protection
 
 
Q