Securing iOS Data at Rest: Protecting the User’s Data

Securing iOS Data at Rest: Protecting the User’s Data

This is the first of three articles on securing user data at rest. In this post, we'll start off with the basics of protecting data on iOS so you can learn the current best practices for storing data securely with Swift.

Any app that saves the user's data has to take care of the security and
privacy of that data. As we've seen with recent data breaches,
there can be very serious consequences for failing to protect your
users' stored data. In this tutorial, you'll learn some best
practices for protecting your users' data.

Permissions

Before we get into storing your custom data, let's take a look at data that can be shared by system apps. 

For many iOS versions, it has been required to request app permissions to use and store some of the user's private data that is external to the app, such as when saving and loading pictures to the photo library. Starting in iOS 10, any APIs that access the user's private data require you to declare that access ahead of time in your project's info.plist file. 

There are many frameworks that can access data outside of your app, and each framework has a corresponding privacy key.

  • Bluetooth Sharing: NSBluetoothPeripheralUsageDescription
  • Calendar: NSCalendarsUsageDescription
  • CallKit: NSVoIPUsageDescription
  • Camera: NSCameraUsageDescription
  • Contacts: NSContactsUsageDescription
  • Health: NSHealthShareUsageDescription, NSHealthUpdateUsageDescription
  • HomeKit: NSHomeKitUsageDescription
  • Location: NSLocationAlwaysUsageDescription, NSLocationUsageDescription, NSLocationWhenInUseUsageDescription
  • Media Library: NSAppleMusicUsageDescription
  • Microphone: NSMicrophoneUsageDescription
  • Motion: NSMotionUsageDescription
  • Photos: NSPhotoLibraryUsageDescription
  • Reminders: NSRemindersUsageDescription
  • Speech Recognition: NSSpeechRecognitionUsageDescription
  • SiriKit: NSSiriUsageDescription
  • TV Provider: NSVideoSubscriberAccountUsageDescription

For example, here is an entry in info.plist to allow your app to load and store values to the calendar.

If a usage description is missing when the API tries to access the data, the app will simply crash.

The Data Protection API

For any user data that's internal to the app, the first thing to think about is whether you need to store the information, and what data is essential to
the app. Keep as much of that essential data in working memory instead
of in file storage. This is especially important for any personally identifiable information. 

But, if you must store data, it's a good idea to enable Apple's Data Protection.

Data
Protection encrypts the contents of your app’s container. It relies on
the user having a passcode, and thus the security of the encryption is
tied to the strength of the passcode. With Touch ID and the upgraded
file system encryption introduced in iOS 10.3, the data protection
system has had many improvements. You can enable data protection across
your app by turning on Data Protection in the Capabilities section
of your project file. This updates your provisioning profile and
entitlements file to include the Data Protection capability. Data
Protection offers four levels of protection, depicted by the FileProtectionType structure:

  • none: no protection.
  • complete: data is not accessible while the device is locked. This is the recommended setting for most applications.
  • completeUnlessOpen: data
    is accessible when the device is unlocked, and continues to be
    accessible until the file is closed, even if the user locks the device. Files can also be created
    when the device is locked. This option is good for when you need to open a file to process and have the process continue even if the user puts the app into the background and locks the device. An example might be a job that uploads a file to a server.
  • completeUntilFirstUserAuthentication: when
    the device is booted, files are not accessible until the user first
    unlocks the device. After that, files are available even when the device
    is locked again. The option is good for files that need to be accessed sometime later in the background when the device is locked, such as during a background fetch job.

complete is the default level. To help
avoid crashes when your code tries to access data that is locked, you
can register for notifications via UIApplicationProtectedDataDidBecomeAvailable and UIApplicationProtectedDataWillBecomeUnavailable to find out when the data is available.

Additionally, you can also check the UIApplication.shared.isProtectedDataAvailable flag.

One important thing to keep in mind when enabling data protection is
that if you are using any background services such as background fetch,
that code may need access to your data in the background when the device
is locked. For those files, you will need to set a protection level of completeUntilFirstUserAuthentication. You can control the protection level of each file individually when creating files and directories using the FileManager class.

You can also set the protection level when you write to a file. The Data object has a method that can write its data to a file, and you can set the protection level when you call this method.

You can also set the protection level when setting up your Core Data model.

To change the protection level of an existing file, use the following:

Data Integrity

Part of protecting your stored data includes checking
its integrity. It's good practice not to blindly trust the data you
are loading from storage; it may have been accidentally or maliciously
altered. The NSSecureCoding protocol can be used to
safely load and save your data objects from storage. It will make sure the objects you load contain the expected data. If you will be saving your own object, you can conform
to the secure coding protocol inside your class.

The class must be inherited from NSObject. Then, to turn on secure coding, override the supportsSecureCoding protocol method.

If your custom object is deserialized with init?(coder aDecoder: NSCoder), the decodeObject(forKey:) method should be replaced with decodeObject(of:forKey:), which makes sure that the correct object types are unpacked from storage.

If you are using NSKeyedUnarchiver to load data from storage, make sure to set its requiresSecureCoding property.

Turning on secure coding for your save operations will prevent
you from accidentally archiving an object that does not adhere to the
secure coding protocol.

Beyond NSSecureCoding, it’s always
good to implement your own data validation checks upon unpacking any
archive or receiving any arbitrary input in general.

Data Trails

As iOS continues to evolve, there are always new features
that have the potential to leak stored data. Starting in iOS 9, you can have your content indexed in the Spotlight search, and on iOS 10 you can expose your content to Widgets such as the Today Widget that shows up on the lock screen. Use caution if you would like to expose your content with these new features. You might end up sharing more than you planned to!

iOS 10 also adds a new Handoff feature where your copied pasteboard data is automatically shared between devices. Again, be careful not to expose any sensitive data in the pasteboard to Handoff. You can do this by marking the sensitive content as localOnly. You can also set an expiry date and time for the data.

Files that are saved to the
device's storage can automatically get backed up, either in iTunes or in
iCloud. Even though backups can be encrypted, it's a good idea to
exclude any sensitive files that don't even need to leave the device. This can be done by setting the isExcludedFromBackup flag on the file.

The animation that happens when putting an app into the background
is achieved by iOS taking a screenshot of your app which it then uses
for the animation. When you look at the list of open apps on the app
switcher, this screenshot is used there too. The screenshot gets stored
on the device. 

It's a good idea to hide any views
revealing sensitive data so that the data isn't captured in the
screenshot. To do this, set up a notification when the application is
going to the background and set the hidden property for the UI
elements you want to exclude. They will be hidden before iOS captures
the screen. Then when coming to the foreground, you can unhide the UI
elements.

Remove your notifications when the view disappears.

Your
app also has a keyboard cache for text fields that have auto-correct
enabled. Text that the user types, along with newly learned words, are
stored in the cache so that it is possible to retrieve various words
that the user has previously entered in your application. The only way
to disable the keyboard cache is to turn off the auto-correct option.

You should mark password fields as secure text entry. Secure text fields don't display the password or use the keyboard cache.

Debug
logs are saved to a file and could be retrieved for production builds
of your app. Even when you're coding and debugging your app, make sure not to log sensitive information such as
passwords and keys to the console. You might forget to remove
that information from the logs before submitting your code to the app store! While debugging,
it's safer instead to use a breakpoint to view sensitive variables.

Network
connections may also get cached to storage. More information about
removing and disabling the network cache can be found in the article Securing Communications on iOS.

Destroying Data

You may already know that when a file on a
computer is deleted, often the file itself is not removed; only the
reference for the file is removed. To actually remove the file, you can
overwrite the file with random data before removing it. 

The switch to
solid state drives has made it hard to guarantee the data has been
destroyed, and the best way to securely delete data is open to debate.
However, this tutorial would not be complete without an example of how
to wipe data from storage. Because of some other debates about the Swift optimizer, and because we hope to guarantee that
each byte of the file is actually being overwritten, we are implementing
this function in C. 

The implementation below can go inside a .c file.
You will need to add the function definition or the file that contains the function into your bridging header
in order to use the function from Swift. You may then want to call this function right before
places where you use FileManager's removeFile methods.
Perhaps you may want to implement the best practices described in this and the upcoming
tutorials on an app update. You could then wipe the previous
unprotected data during migration.

Conclusion

In this article, you have learned about setting permissions for the data that your app has access to, as well as how to ensure basic file protection and integrity. We also looked at some ways that user data could be leaked accidentally from your app. Your
users put their trust in you to protect their data. Following these best
practices will help you repay that confidence.

While you're here, check out some of our other posts on iOS app development!

Source: Tuts Plus

About the Author