-
Notifications
You must be signed in to change notification settings - Fork 2.9k
/
EncryptedJSON.swift
170 lines (141 loc) · 4.47 KB
/
EncryptedJSON.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import Foundation
import Account
import Shared
import FxA
import Account
import XCGLogger
import SwiftyJSON
private let log = Logger.syncLogger
/**
* Turns JSON of the form
*
* { ciphertext: ..., hmac: ..., iv: ...}
*
* into a new JSON object resulting from decrypting and parsing the ciphertext.
*/
open class EncryptedJSON {
var json: JSON
var _cleartext: JSON? // Cache decrypted cleartext.
var _ciphertextBytes: Data? // Cache decoded ciphertext.
var _hmacBytes: Data? // Cache decoded HMAC.
var _ivBytes: Data? // Cache decoded IV.
var valid: Bool = false
var validated: Bool = false
let keyBundle: KeyBundle
public init(json: String, keyBundle: KeyBundle) {
self.keyBundle = keyBundle
self.json = JSON(parseJSON: json)
}
public init(json: JSON, keyBundle: KeyBundle) {
self.keyBundle = keyBundle
self.json = json
}
// For validating HMAC: the raw ciphertext as bytes without decoding.
fileprivate var ciphertextB64: Data? {
if let ct = self["ciphertext"].string {
return Bytes.dataFromBase64(ct)
}
return nil
}
/**
* You probably want to call validate() and then use .ciphertext.
*/
fileprivate var ciphertextBytes: Data? {
return Bytes.decodeBase64(self["ciphertext"].string!)
}
fileprivate func validate() -> Bool {
if validated {
return valid
}
defer { validated = true }
guard self["ciphertext"].isString() &&
self["hmac"].isString() &&
self["IV"].isString() else {
valid = false
return false
}
guard let ciphertextForHMAC = self.ciphertextB64 else {
valid = false
return false
}
guard keyBundle.verify(hmac: self.hmac, ciphertextB64: ciphertextForHMAC) else {
valid = false
return false
}
// I guess we called validate twice…
if self._ciphertextBytes != nil {
valid = true
return true
}
// Also verify that the ciphertext is valid base64. Do this by
// retrieving the value in a failable way, leaving the accessors
// to take the dangerous/simple path.
// We can force-unwrap self["ciphertext"] because we already checked
// it when verifying the HMAC above.
guard let data = self.ciphertextBytes else {
log.error("Unable to decode ciphertext base64 in record \(self["id"].string ?? "<unknown>")")
valid = false
return false
}
self._ciphertextBytes = data
valid = true
return valid
}
open func isValid() -> Bool {
return !json.isError() && self.validate()
}
/**
* Make sure you call isValid first. This API force-unwraps for simplicity.
*/
var ciphertext: Data {
if _ciphertextBytes != nil {
return _ciphertextBytes!
}
_ciphertextBytes = self.ciphertextBytes
return _ciphertextBytes!
}
var hmac: Data {
if _hmacBytes != nil {
return _hmacBytes!
}
//NSData(base16EncodedString: self["hmac"].asString!, options: NSDataBase16DecodingOptions.Default)
_hmacBytes = NSData(base16EncodedString: self["hmac"].stringValue, options: []) as Data
return _hmacBytes!
}
var iv: Data {
if _ivBytes != nil {
return _ivBytes!
}
_ivBytes = Bytes.decodeBase64(self["IV"].string!)
return _ivBytes!
}
// Returns nil on error.
open var cleartext: JSON? {
if _cleartext != nil {
return _cleartext
}
if !isValid() {
log.error("Failed to validate.")
return nil
}
let decrypted: String? = keyBundle.decrypt(self.ciphertext, iv: self.iv)
if decrypted == nil {
log.error("Failed to decrypt.")
valid = false
return nil
}
_cleartext = JSON(parseJSON: decrypted!)
return _cleartext!
}
subscript(key: String) -> JSON {
get {
return json[key]
}
set {
json[key] = newValue
}
}
}