With iOS 12 Apple has started adopting NSSecureCoding
across the entire platform. For Core Data this means that the default ValueTransformer
, which uses NSCoding
to transform a custom data type into a format that can be stored in the persistent store, at some point will change as well.
Because of this, you might have seen the following warning in your project:
CoreData: One or more models in this application are using transformable properties with transformer names that are either unset, or set to NSKeyedUnarchiveFromDataTransformerName. Please switch to using “NSSecureUnarchiveFromData” or a subclass of NSSecureUnarchiveFromDataTransformer instead. At some point, Core Data will default to using “NSSecureUnarchiveFromData” when nil is specified, and transformable properties containing classes that do not support NSSecureCoding will become unreadable.
This week, let’s have a look at what this warning means and how we can solve it. We’ll start by going over some of the concepts of transformable properties and object serialization.
Transformable properties
Out of the box, Core Data supports a variety of data types, for example, integers, booleans, strings, etc. In most cases this is sufficient, but sometimes you want to store data of a different type for example, UIColor
. You can do this by creating a Transformable
property. When you declare a property as Transformable
Core Data converts your custom data type into binary Data
when it is saved to the persistent store and converts it back to your custom data type when fetched from the store. It does this through a value transformer.
A ValueTransformer
is simply a class that transforms a value into another one. When you don’t define a custom transformer for your transformable property Core Data will use the default one. The default transformer works with NSCoding
, a protocol that you implement on custom data types so it can be encoded and decoded. Encoding and decoding itself is done by NSKeyedArchiver
and NSKeyedUnarchiver
, which conforms to NSCoder
. Because of this, the default transformer works on any type that conforms to the NSCoding
protocol.
NSSecureCoding
One limitation of NSCoding
is that it is vulnerable to substitution attacks. A substitution attack is when an attacker alters data, for example, stored on disk, with the goal of getting the app into a certain state in which it can be exploited. NSCoding
is vulnerable for such an attack because it does not allow you to verify the class type before the object is constructed. At this point, the damage could already have been done.
/// Here we unarchive the data and validate that the object has the right structure.
/// As you can see we first need to construct `MyCustomObject` before we can validate it.
if let myCustomObject = decoder.decodeObjectForKey("myCustomObject") as MyCustomObject {
// Use the custom type
}
NSSecureCoding
fixes this by allowing you to pass in the type information upfront, so that the archiver can do a class check before unarchiving. You can do this with the unarchivedObject(ofClass:from:)
method on NSKeyedUnarchiver
. If during unarchiving it encounters a different type of class or one that does not conform to NSSecureCoding
it will stop and throw an exception. This will prevent bad things from happening.
Solving the warning
Now let’s move back to the warning. In the first sentence, it informs us that our data model contains one or more entities with a transformable property that uses the default value transformer. Either because we did not specify anything in the transformer
field of the data model inspector or because it is set to
NSKeyedUnarchiveFromDataTransformerName
.
It then tells us that in the future this will default to the NSSecureUnarchiveFromData
transformer, which works with NSSecureCoding
. And when that happens all transformable properties that do not adopt secure coding will become unreadable.
Conforming to NSSecureCoding
This means that the first thing we need to do is make sure that the data type of every transformable property in our data model conforms to secure coding. A lot of system types already support secure coding, but if it is a custom type you’ll need to do two things:
- Conform to the
NSSecureCoding
protocol:
final class MyCustomObject: NSSecureCoding {
static var supportsSecureCoding = true
}
- Replace every call to
decodeObject(forKey:)
in your implementation ofinit(coder:)
withdecodeObjectOfClass:forKey:
.
Setting a ValueTransformer
The new default value transformer, named NSSecureUnarchiveFromData
, out of the box supports all plist types, for example, NSDate
and NSString
. So If you’re transformable property fits that requirement you can start using the new default transformer as follows:
For non-plist type, for example, UIColor
, Core Data won’t be able to figure out which class the unarchiver should check for and will throw an exception. Because Swift doesn’t allow us to catch Objective-c and C++ exceptions this will make our app crash with a fatalError
. We can solve this by creating a custom value transformer. Here is an example of a simple implementation:
// 1. Subclass from `NSSecureUnarchiveFromDataTransformer`
@objc(UIColorValueTransformer)
final class ColorValueTransformer: NSSecureUnarchiveFromDataTransformer {
/// The name of the transformer. This is the name used to register the transformer using `ValueTransformer.setValueTrandformer(_"forName:)`.
static let name = NSValueTransformerName(rawValue: String(describing: ColorValueTransformer.self))
// 2. Make sure `UIColor` is in the allowed class list.
override static var allowedTopLevelClasses: [AnyClass] {
return [UIColor.self]
}
/// Registers the transformer.
public static func register() {
let transformer = ColorValueTransformer()
ValueTransformer.setValueTransformer(transformer, forName: name)
}
}
Here’s what is happening:
-
Just like the default transformer, we subclass from
NSSecureUnarchiveFromDataTransformer
. We need to use the@objc
attribute to instruct Swift to make this class accessible and usable in Objective-C. Otherwise Core Data won’t be able to use the value transformer as the framework is written in Objective-C. -
Make sure
UIColor
is in the allowed class list. This is the list that the archiver will check against when archiving or unarchiving the data.
The next thing you need to do is make sure Core Data knows that it should use the ColorValueTransformer
for your transformable property.
The last step is to make the value transformer available by registering it through the register
class function that we created. A good time to do this is right before setting up the persistent container.
Conclusion
Now that you know how all of this works you can go ahead and fix the warnings in your project. While you do that, it is a good idea to update any of your existing value transformers so that they also use NSSecureCoding
. Doing this will make your app more secure.
Contact me on Twitter @kairadiagne if you have any questions, comments or feedback.