MASTG-KNOW-0075: Object Serialization
There are several ways to persist an object on iOS:
Object Encoding¶
iOS comes with two protocols for object encoding and decoding for Objective-C or NSObject
s: NSCoding
and NSSecureCoding
. When a class conforms to either of the protocols, the data is serialized to NSData
: a wrapper for byte buffers. Note that Data
in Swift is the same as NSData
or its mutable counterpart: NSMutableData
. The NSCoding
protocol declares the two methods that must be implemented in order to encode/decode its instance-variables. A class using NSCoding
needs to implement NSObject
or be annotated as an @objc class. The NSCoding
protocol requires to implement encode and init as shown below.
class CustomPoint: NSObject, NSCoding {
//required by NSCoding:
func encode(with aCoder: NSCoder) {
aCoder.encode(x, forKey: "x")
aCoder.encode(name, forKey: "name")
}
var x: Double = 0.0
var name: String = ""
init(x: Double, name: String) {
self.x = x
self.name = name
}
// required by NSCoding: initialize members using a decoder.
required convenience init?(coder aDecoder: NSCoder) {
guard let name = aDecoder.decodeObject(forKey: "name") as? String
else {return nil}
self.init(x:aDecoder.decodeDouble(forKey:"x"),
name:name)
}
//getters/setters/etc.
}
The issue with NSCoding
is that the object is often already constructed and inserted before you can evaluate the class-type. This allows an attacker to easily inject all sorts of data. Therefore, the NSSecureCoding
protocol has been introduced. When conforming to NSSecureCoding
you need to include:
static var supportsSecureCoding: Bool {
return true
}
when init(coder:)
is part of the class. Next, when decoding the object, a check should be made, e.g.:
let obj = decoder.decodeObject(of:MyClass.self, forKey: "myKey")
The conformance to NSSecureCoding
ensures that objects being instantiated are indeed the ones that were expected. However, there are no additional integrity checks done over the data and the data is not encrypted. Therefore, any secret data needs additional encryption and data of which the integrity must be protected, should get an additional HMAC.
Note, when NSData
(Objective-C) or the keyword let
(Swift) is used: then the data is immutable in memory and cannot be easily removed.
Object Archiving with NSKeyedArchiver¶
NSKeyedArchiver
is a concrete subclass of NSCoder
and provides a way to encode objects and store them in a file. The NSKeyedUnarchiver
decodes the data and recreates the original data. Let's take the example of the NSCoding
section and now archive and unarchive them:
// archiving:
NSKeyedArchiver.archiveRootObject(customPoint, toFile: "/path/to/archive")
// unarchiving:
guard let customPoint = NSKeyedUnarchiver.unarchiveObjectWithFile("/path/to/archive") as?
CustomPoint else { return nil }
When decoding a keyed archive, because values are requested by name, values can be decoded out of sequence or not at all. Keyed archives, therefore, provide better support for forward and backward compatibility. This means that an archive on disk could actually contain additional data which is not detected by the program, unless the key for that given data is provided at a later stage.
Note that additional protection needs to be in place to secure the file in case of confidential data, as the data is not encrypted within the file. See the chapter "Data Storage on iOS" for more details.
Codable¶
With Swift 4, the Codable
type alias arrived: it is a combination of the Decodable
and Encodable
protocols. A String
, Int
, Double
, Date
, Data
and URL
are Codable
by nature: meaning they can easily be encoded and decoded without any additional work. Let's take the following example:
struct CustomPointStruct:Codable {
var x: Double
var name: String
}
By adding Codable
to the inheritance list for the CustomPointStruct
in the example, the methods init(from:)
and encode(to:)
are automatically supported. Fore more details about the workings of Codable
check the Apple Developer Documentation.
The Codable
s can easily be encoded / decoded into various representations: NSData
using NSCoding
/NSSecureCoding
, JSON, Property Lists, XML, etc. See the subsections below for more details.
JSON and Codable¶
There are various ways to encode and decode JSON within iOS by using different third-party libraries:
- Mantle
- JSONModel library
- SwiftyJSON library
- ObjectMapper library
- JSONKit
- JSONModel
- YYModel
- SBJson 5
- Unbox
- Gloss
- Mapper
- JASON
- Arrow
The libraries differ in their support for certain versions of Swift and Objective-C, whether they return (im)mutable results, speed, memory consumption and actual library size. Again, note in case of immutability: confidential information cannot be removed from memory easily.
Next, Apple provides support for JSON encoding/decoding directly by combining Codable
together with a JSONEncoder
and a JSONDecoder
:
struct CustomPointStruct: Codable {
var point: Double
var name: String
}
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let test = CustomPointStruct(point: 10, name: "test")
let data = try encoder.encode(test)
let stringData = String(data: data, encoding: .utf8)
// stringData = Optional ({
// "point" : 10,
// "name" : "test"
// })
JSON itself can be stored anywhere, e.g., a (NoSQL) database or a file. You just need to make sure that any JSON that contains secrets has been appropriately protected (e.g., encrypted/HMACed). See the chapter "Data Storage on iOS" for more details.
Property Lists and Codable¶
You can persist objects to property lists (also called plists in previous sections). You can find two examples below of how to use it:
// archiving:
let data = NSKeyedArchiver.archivedDataWithRootObject(customPoint)
NSUserDefaults.standardUserDefaults().setObject(data, forKey: "customPoint")
// unarchiving:
if let data = NSUserDefaults.standardUserDefaults().objectForKey("customPoint") as? NSData {
let customPoint = NSKeyedUnarchiver.unarchiveObjectWithData(data)
}
In this first example, the NSUserDefaults
are used, which is the primary property list. We can do the same with the Codable
version:
struct CustomPointStruct: Codable {
var point: Double
var name: String
}
var points: [CustomPointStruct] = [
CustomPointStruct(point: 1, name: "test"),
CustomPointStruct(point: 2, name: "test"),
CustomPointStruct(point: 3, name: "test"),
]
UserDefaults.standard.set(try? PropertyListEncoder().encode(points), forKey: "points")
if let data = UserDefaults.standard.value(forKey: "points") as? Data {
let points2 = try? PropertyListDecoder().decode([CustomPointStruct].self, from: data)
}
Note that plist
files are not meant to store secret information. They are designed to hold user preferences for an app.
XML¶
There are multiple ways to do XML encoding. Similar to JSON parsing, there are various third party libraries, such as:
They vary in terms of speed, memory usage, object persistence and more important: differ in how they handle XML external entities. See XXE in the Apple iOS Office viewer as an example. Therefore, it is key to disable external entity parsing if possible. See the OWASP XXE prevention cheatsheet for more details.
Next to the libraries, you can make use of Apple's XMLParser
class
When not using third party libraries, but Apple's XMLParser
, be sure to let shouldResolveExternalEntities
return false
.
Object-Relational Mapping (CoreData and Realm)¶
There are various ORM-like solutions for iOS. The first one is Realm, which comes with its own storage engine. Realm has settings to encrypt the data as explained in Realm's documentation. This allows for handling secure data. Note that the encryption is turned off by default.
Apple itself supplies CoreData
, which is well explained in the Apple Developer Documentation. It supports various storage backends as described in Apple's Persistent Store Types and Behaviors documentation. The issue with the storage backends recommended by Apple, is that none of the type of data stores is encrypted, nor checked for integrity. Therefore, additional actions are necessary in case of confidential data. An alternative can be found in project iMas, which does supply out of the box encryption.
Protocol Buffers¶
Protocol Buffers by Google, are a platform- and language-neutral mechanism for serializing structured data by means of the Binary Data Format. They are available for iOS by means of the Protobuf library. There have been a few vulnerabilities with Protocol Buffers, such as CVE-2015-5237. Note that Protocol Buffers do not provide any protection for confidentiality as no built-in encryption is available.