MASTG-KNOW-0075: Serialização de Objetos
Existem várias maneiras de persistir um objeto em iOS:
Codificação de Objetos¶
O iOS possui dois protocolos para codificação e decodificação de objetos em Objective-C ou NSObjects: NSCoding e NSSecureCoding. Quando uma classe está em conformidade com um desses protocolos, os dados são serializados para NSData: um wrapper para buffers de bytes. Observe que Data em Swift é o mesmo que NSData ou sua contraparte mutável: NSMutableData. O protocolo NSCoding declara os dois métodos que devem ser implementados para codificar/decodificar suas variáveis de instância. Uma classe que usa NSCoding precisa implementar NSObject ou ser anotada como uma classe @objc. O protocolo NSCoding requer a implementação de encode e init, conforme mostrado abaixo.
class CustomPoint: NSObject, NSCoding {
// requerido por 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
}
// requerido por NSCoding: inicializa membros usando um decodificador.
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.
}
O problema com NSCoding é que o objeto geralmente já está construído e inserido antes que você possa avaliar o tipo de classe. Isso permite que um invasor injete facilmente todos os tipos de dados. Portanto, o protocolo NSSecureCoding foi introduzido. Ao estar em conformidade com NSSecureCoding, você precisa incluir:
static var supportsSecureCoding: Bool {
return true
}
quando init(coder:) faz parte da classe. Em seguida, ao decodificar o objeto, uma verificação deve ser feita, por exemplo:
let obj = decoder.decodeObject(of:MyClass.self, forKey: "myKey")
A conformidade com NSSecureCoding garante que os objetos sendo instanciados são realmente os esperados. No entanto, não há verificações de integridade adicionais feitas sobre os dados e os dados não são criptografados. Portanto, quaisquer dados secretos precisam de criptografia adicional e dados cuja integridade deve ser protegida devem receber um HMAC adicional.
Observe: quando NSData (Objective-C) ou a palavra-chave let (Swift) são usados, os dados são imutáveis na memória e não podem ser facilmente removidos.
Arquivamento de Objetos com NSKeyedArchiver¶
NSKeyedArchiver é uma subclasse concreta de NSCoder e fornece uma maneira de codificar objetos e armazená-los em um arquivo. O NSKeyedUnarchiver decodifica os dados e recria os dados originais. Vamos pegar o exemplo da seção NSCoding e agora arquivá-los e desarquivá-los:
// arquivamento:
NSKeyedArchiver.archiveRootObject(customPoint, toFile: "/path/to/archive")
// desarquivamento:
guard let customPoint = NSKeyedUnarchiver.unarchiveObjectWithFile("/path/to/archive") as?
CustomPoint else { return nil }
Ao decodificar um arquivo chaveado, como os valores são solicitados por nome, os valores podem ser decodificados fora de sequência ou não serem decodificados. Arquivos chaveados, portanto, fornecem melhor suporte para compatibilidade direta e reversa. Isso significa que um arquivo em disco poderia conter dados adicionais que não são detectados pelo programa, a menos que a chave para esses dados seja fornecida em um estágio posterior.
Observe que proteção adicional precisa estar em vigor para proteger o arquivo em caso de dados confidenciais, pois os dados não são criptografados dentro do arquivo. Consulte o capítulo "Armazenamento de Dados em iOS" para mais detalhes.
Codable¶
Com o Swift 4, chegou o alias de tipo Codable: é uma combinação dos protocolos Decodable e Encodable. String, Int, Double, Date, Data e URL são Codable por natureza: significando que podem ser facilmente codificados e decodificados sem qualquer trabalho adicional. Vamos pegar o seguinte exemplo:
struct CustomPointStruct:Codable {
var x: Double
var name: String
}
Ao adicionar Codable à lista de herança para CustomPointStruct no exemplo, os métodos init(from:) e encode(to:) são automaticamente suportados. Para mais detalhes sobre o funcionamento de Codable, verifique a documentação de desenvolvedor da Apple.
Os Codables podem ser facilmente codificados/decodificados em várias representações: NSData usando NSCoding/NSSecureCoding, JSON, Property Lists, XML, etc. Veja as subseções abaixo para mais detalhes.
JSON e Codable¶
Existem várias maneiras de codificar e decodificar JSON dentro do iOS usando diferentes bibliotecas de terceiros:
- Mantle
- JSONModel library
- SwiftyJSON library
- ObjectMapper library
- JSONKit
- JSONModel
- YYModel
- SBJson 5
- Unbox
- Gloss
- Mapper
- JASON
- Arrow
As bibliotecas diferem em seu suporte para certas versões do Swift e Objective-C, se retornam resultados (im)mutáveis, velocidade, consumo de memória e tamanho real da biblioteca. Novamente, observe no caso de imutabilidade: informações confidenciais não podem ser facilmente removidas da memória.
Em seguida, a Apple fornece suporte para codificação/decodificação JSON diretamente combinando Codable com um JSONEncoder e um 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"
// })
O JSON em si pode ser armazenado em qualquer lugar, por exemplo, um banco de dados (NoSQL) ou um arquivo. Você só precisa garantir que qualquer JSON que contenha segredos tenha sido apropriadamente protegido (por exemplo, criptografado/com HMAC). Consulte o capítulo "Armazenamento de Dados em iOS" para mais detalhes.
Property Lists e Codable¶
Você pode persistir objetos em property lists (também chamadas de plists em seções anteriores). Você pode encontrar dois exemplos abaixo de como usá-las:
// arquivamento:
let data = NSKeyedArchiver.archivedDataWithRootObject(customPoint)
NSUserDefaults.standardUserDefaults().setObject(data, forKey: "customPoint")
// desarquivamento:
if let data = NSUserDefaults.standardUserDefaults().objectForKey("customPoint") as? NSData {
let customPoint = NSKeyedUnarchiver.unarchiveObjectWithData(data)
}
Neste primeiro exemplo, os NSUserDefaults são usados, que são a principal property list. Podemos fazer o mesmo com a versão Codable:
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)
}
Observe que arquivos plist não são destinados a armazenar informações secretas. Eles são projetados para manter as preferências do usuário para um aplicativo.
XML¶
Existem várias maneiras de fazer codificação XML. Semelhante ao parsing de JSON, existem várias bibliotecas de terceiros, como:
Elas variam em termos de velocidade, uso de memória, persistência de objetos e, mais importante: diferem em como lidam com entidades externas XML. Veja XXE no visualizador de Office para iOS da Apple como exemplo. Portanto, é fundamental desativar o parsing de entidades externas, se possível. Consulte a OWASP XXE prevention cheatsheet para mais detalhes.
Além das bibliotecas, você pode fazer uso da classe XMLParser da Apple.
Ao não usar bibliotecas de terceiros, mas o XMLParser da Apple, certifique-se de que shouldResolveExternalEntities retorne false.
Mapeamento Objeto-Relacional (CoreData e Realm)¶
Existem várias soluções semelhantes a ORM para iOS. A primeira é Realm, que vem com seu próprio mecanismo de armazenamento. O Realm possui configurações para criptografar os dados, conforme explicado na documentação do Realm. Isso permite o manuseio de dados seguros. Observe que a criptografia é desativada por padrão.
A própria Apple fornece CoreData, que é bem explicado na Documentação de Desenvolvedor da Apple. Ele suporta vários backends de armazenamento, conforme descrito na documentação de Tipos e Comportamentos de Persistent Store da Apple. O problema com os backends de armazenamento recomendados pela Apple é que nenhum dos tipos de armazenamento de dados é criptografado, nem verificado quanto à integridade. Portanto, ações adicionais são necessárias em caso de dados confidenciais. Uma alternativa pode ser encontrada no project iMas, que fornece criptografia pronta para uso.
Protocol Buffers¶
Protocol Buffers do Google, são um mecanismo neutro em plataforma e linguagem para serializar dados estruturados por meio do Formato de Dados Binários. Eles estão disponíveis para iOS por meio da biblioteca Protobuf.
Houve algumas vulnerabilidades com Protocol Buffers, como CVE-2015-5237. Observe que Protocol Buffers não fornecem nenhuma proteção para confidencialidade, pois não há criptografia incorporada disponível.