]> git.saurik.com Git - apple/security.git/blob - keychain/TrustedPeersHelperUnitTests/TrustedPeersHelperUnitTests.swift
Security-59306.61.1.tar.gz
[apple/security.git] / keychain / TrustedPeersHelperUnitTests / TrustedPeersHelperUnitTests.swift
1 //
2 // TrustedPeersHelperUnitTests.swift
3 // TrustedPeersHelperUnitTests
4 //
5 // Created by Ben Williamson on 5/1/18.
6 //
7
8 import CoreData
9 import XCTest
10
11 let testDSID = "123456789"
12
13 let signingKey_384 = Data(base64Encoded: "BOQbPoiBnzuA0Cgc2QegjKGJqDtpkRenHwAxkYKJH1xELdaoIh8ifSch8sl18tpBYVUpEfdxz2ZSKif+dx7UPfu8WeTtpHkqm3M+9PTjr/KNNJCSR1PQNB5Jh+sRiQ+cpJnoTzm+IZSIukylamAcL3eA0nMUM0Zc2u4TijrbTgVND22WzSirUkwSK3mA/prk9A==")
14
15 let encryptionKey_384 = Data(base64Encoded: "BE1RuazBWmSEx0XVGhobbrdSE6fRQOrUrYEQnBkGl4zJq9GCeRoYvbuWNYFcOH0ijCRz9pYILsTn3ajT1OknlvcKmuQ7SeoGWzk9cBZzT5bBEwozn2gZxn80DQoDkmejywlH3D0/cuV6Bxexu5KMAFGqg6eN6th4sQABL5EuI9zKPuxHStM/b9B1LyqcnRKQHA==")
16
17 let symmetricKey_384 = Data(base64Encoded: "MfHje3Y/mWV0q+grjwZ4VxuqB7OreYHLxYkeeCiNjjY=")
18
19 let recovery_signingKey_384 = Data(base64Encoded: "BK5nrmP6oitJHtGV2Josk5cUKnG3pqxgEP8uzyPtNXgAMNHZoDKwCKFXpUzQSgbYiR4G2XZY2Q0+qSCKN7YSY2KNKE0hM9p4GvABBmAWKW/O9eFd5ugKQWisn25a/7nieIw8CQ81hBDR7R/vBpfLVtzE8ieRA8JPGqulQ5RdLcClFrD3B8BPJAZpLv4OP1CLDA==")
20
21 let recovery_encryptionKey_384 = Data(base64Encoded: "BKkZpYHTbMi2yrWFo+ErM3HbcYJCngPuWDYoVUD7egKkmiHFvv1Bsk0j/Dcj3xTR12vj5QOpZQV3GzE5estf75BV+EZz1cjUUSi/MysfpKsqEbwYrhIEkmeyMGr7CVWQWRLR2LnoihnQajvWi1LmO0AoDl3+LzVgTJBjjDQ5ANyw0Yv1EgOgBvZsLA9UTN4oAg==")
22
23 class TrustedPeersHelperUnitTests: XCTestCase {
24
25 var tmpPath: String!
26 var tmpURL: URL!
27 var cuttlefish: FakeCuttlefishServer!
28
29 var manateeKeySet: CKKSKeychainBackedKeySet!
30
31 override static func setUp() {
32 super.setUp()
33
34 SecTapToRadar.disableTTRsEntirely()
35
36 // Turn on NO_SERVER stuff
37 securityd_init_local_spi()
38
39 SecCKKSDisable()
40 }
41
42 override func setUp() {
43 super.setUp()
44
45 let testName = self.name.components(separatedBy: CharacterSet(charactersIn: " ]"))[1]
46 cuttlefish = FakeCuttlefishServer(nil, ckZones: [:], ckksZoneKeys: [:])
47
48 // Make a new fake keychain
49 tmpPath = String(format: "/tmp/%@-%X", testName, arc4random())
50 tmpURL = URL(fileURLWithPath: tmpPath, isDirectory: true)
51 do {
52 try FileManager.default.createDirectory(atPath: String(format: "%@/Library/Keychains", tmpPath), withIntermediateDirectories: true, attributes: nil)
53 SetCustomHomeURLString(tmpPath as CFString)
54 SecKeychainDbReset(nil)
55 } catch {
56 XCTFail("setUp failed: \(error)")
57 }
58
59 // Actually load the database.
60 kc_with_dbt(true, nil) { _ in
61 false
62 }
63
64 // Now that the keychain is alive, perform test setup
65 do {
66 self.manateeKeySet = try self.makeFakeKeyHierarchy(zoneID: CKRecordZone.ID(zoneName: "Manatee"))
67 } catch {
68 XCTFail("Creation of fake key hierarchies failed: \(error)")
69 }
70 }
71
72 override func tearDown() {
73 // Put teardown code here. This method is called after the invocation of each test method in the class.
74 cuttlefish = nil
75 super.tearDown()
76 }
77
78 func makeFakeKeyHierarchy(zoneID: CKRecordZone.ID) throws -> CKKSKeychainBackedKeySet {
79 // Remember, these keys come into TPH having round-tripped through an NSEncoding
80 let tlk = try CKKSKeychainBackedKey.randomKeyWrapped(bySelf: zoneID)
81 let classA = try CKKSKeychainBackedKey.randomKeyWrapped(byParent: tlk, keyclass: SecCKKSKeyClassA)
82 let classC = try CKKSKeychainBackedKey.randomKeyWrapped(byParent: tlk, keyclass: SecCKKSKeyClassC)
83
84 XCTAssertNoThrow(try tlk.saveMaterialToKeychain(), "Should be able to save TLK to keychain")
85 XCTAssertNoThrow(try classA.saveMaterialToKeychain(), "Should be able to save classA key to keychain")
86 XCTAssertNoThrow(try classC.saveMaterialToKeychain(), "Should be able to save classC key to keychain")
87
88 let tlkData = try NSKeyedArchiver.archivedData(withRootObject: tlk, requiringSecureCoding: true)
89 let classAData = try NSKeyedArchiver.archivedData(withRootObject: classA, requiringSecureCoding: true)
90 let classCData = try NSKeyedArchiver.archivedData(withRootObject: classC, requiringSecureCoding: true)
91
92 let decodedTLK = try NSKeyedUnarchiver.unarchivedObject(ofClasses: [CKKSKeychainBackedKey.classForKeyedUnarchiver()], from: tlkData) as! CKKSKeychainBackedKey
93 let decodedClassA = try NSKeyedUnarchiver.unarchivedObject(ofClasses: [CKKSKeychainBackedKey.classForKeyedUnarchiver()], from: classAData) as! CKKSKeychainBackedKey
94 let decodedClassC = try NSKeyedUnarchiver.unarchivedObject(ofClasses: [CKKSKeychainBackedKey.classForKeyedUnarchiver()], from: classCData) as! CKKSKeychainBackedKey
95
96 return CKKSKeychainBackedKeySet(tlk: decodedTLK, classA: decodedClassA, classC: decodedClassC, newUpload: false)
97 }
98
99 func assertTLKShareFor(peerID: String, keyUUID: String, zoneID: CKRecordZone.ID) {
100 let matches = self.cuttlefish.state.tlkShares[zoneID]?.filter { tlkShare in
101 tlkShare.receiver == peerID &&
102 tlkShare.keyUuid == keyUUID
103 }
104
105 XCTAssertEqual(matches?.count ?? 0, 1, "Should have one tlk share matching \(peerID) and \(keyUUID)")
106 }
107
108 func assertNoTLKShareFor(peerID: String, keyUUID: String, zoneID: CKRecordZone.ID) {
109 let matches = self.cuttlefish.state.tlkShares[zoneID]?.filter { tlkShare in
110 tlkShare.receiver == peerID &&
111 tlkShare.keyUuid == keyUUID
112 }
113
114 XCTAssertEqual(matches?.count ?? 0, 0, "Should have no tlk share matching \(peerID) and \(keyUUID)")
115 }
116
117 func assertTrusts(context: Container, peerIDs: [String]) {
118 let state = context.getStateSync(test: self)
119 guard let egoPeerID = state.egoPeerID else {
120 XCTFail("context should have an ego peer ID")
121 return
122 }
123
124 guard let dynamicInfo = state.peers[egoPeerID]?.dynamicInfo else {
125 XCTFail("No dynamicInfo for ego peer")
126 return
127 }
128
129 _ = peerIDs.map {
130 XCTAssertTrue(dynamicInfo.includedPeerIDs.contains($0), "Peer should trust \($0)")
131 XCTAssertFalse(dynamicInfo.excludedPeerIDs.contains($0), "Peer should not distrust \($0)")
132 }
133 }
134
135 func assertDistrusts(context: Container, peerIDs: [String]) {
136 let state = context.getStateSync(test: self)
137 guard let egoPeerID = state.egoPeerID else {
138 XCTFail("context should have an ego peer ID")
139 return
140 }
141
142 guard let dynamicInfo = state.peers[egoPeerID]?.dynamicInfo else {
143 XCTFail("No dynamicInfo for ego peer")
144 return
145 }
146
147 _ = peerIDs.map {
148 XCTAssertFalse(dynamicInfo.includedPeerIDs.contains($0), "Peer should not trust \($0)")
149 XCTAssertTrue(dynamicInfo.excludedPeerIDs.contains($0), "Peer should distrust \($0)")
150 }
151 }
152
153 func tmpStoreDescription(name: String) -> NSPersistentStoreDescription {
154 let tmpStoreURL = URL(fileURLWithPath: name, relativeTo: tmpURL)
155 return NSPersistentStoreDescription(url: tmpStoreURL)
156 }
157
158 func establish(reload: Bool,
159 store: NSPersistentStoreDescription) throws -> (Container, String) {
160 return try self.establish(reload: reload, contextID: OTDefaultContext, store: store)
161 }
162
163 func establish(reload: Bool,
164 contextID: String,
165 allowedMachineIDs: Set<String> = Set(["aaa", "bbb", "ccc"]),
166 store: NSPersistentStoreDescription) throws -> (Container, String) {
167 var container = try Container(name: ContainerName(container: "test", context: contextID), persistentStoreDescription: store, cuttlefish: cuttlefish)
168
169 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: allowedMachineIDs, listDifference: allowedMachineIDs.count > 0), "should be able to set allowed machine IDs")
170
171 let (peerID, permanentInfo, permanentInfoSig, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
172 do {
173 let state = container.getStateSync(test: self)
174 XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer")
175 let secret = container.loadSecretSync(test: self, label: peerID!)
176 XCTAssertNotNil(secret, "secret should not be nil")
177 XCTAssertNil(error, "error should be nil")
178 }
179 XCTAssertNotNil(peerID)
180 XCTAssertNotNil(permanentInfo)
181 XCTAssertNotNil(permanentInfoSig)
182 XCTAssertNil(error)
183
184 _ = container.dumpSync(test: self)
185
186 if (reload) {
187 do {
188 container = try Container(name: ContainerName(container: "test", context: contextID), persistentStoreDescription: store, cuttlefish: cuttlefish)
189 } catch {
190 XCTFail()
191 }
192 }
193
194 let (peerID2, _, error2) = container.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
195 XCTAssertNil(error2)
196 XCTAssertNotNil(peerID2)
197
198 _ = container.dumpSync(test: self)
199
200 return (container, peerID!)
201 }
202
203 func testEstablishWithReload() throws {
204 let description = tmpStoreDescription(name: "container.db")
205 let (_, peerID) = try establish(reload: true, store: description)
206
207 assertTLKShareFor(peerID: peerID, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
208 }
209
210 func testEstablishNoReload() throws {
211 let description = tmpStoreDescription(name: "container.db")
212 _ = try establish(reload: false, store: description)
213 }
214
215 func testEstablishNotOnAllowListErrors() throws {
216 let description = tmpStoreDescription(name: "container.db")
217 let container = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
218
219 let (peerID, permanentInfo, permanentInfoSig, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
220 do {
221 let state = container.getStateSync(test: self)
222 XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer")
223 let secret = container.loadSecretSync(test: self, label: peerID!)
224 XCTAssertNotNil(secret, "secret should not be nil")
225 XCTAssertNil(error, "error should be nil")
226 }
227 XCTAssertNotNil(peerID)
228 XCTAssertNotNil(permanentInfo)
229 XCTAssertNotNil(permanentInfoSig)
230 XCTAssertNil(error)
231
232 // Note that an empty machine ID list means "all are allowed", so an establish now will succeed
233
234 // Now set up a machine ID list that positively does not have our peer
235 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa"]), "should be able to set allowed machine IDs")
236
237 let (peerID3, _, error3) = container.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
238 XCTAssertNotNil(peerID3, "Should get a peer when you establish a now allow-listed peer")
239 XCTAssertNil(error3, "Should not get an error when you establish a now allow-listed peer")
240 }
241
242 func joinByVoucher(sponsor: Container,
243 containerID: String,
244 machineID: String,
245 machineIDs: Set<String>,
246 store: NSPersistentStoreDescription) throws -> (Container, String) {
247 let c = try Container(name: ContainerName(container: containerID, context: OTDefaultContext),
248 persistentStoreDescription: store,
249 cuttlefish: cuttlefish)
250
251 XCTAssertNil(c.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, listDifference: machineIDs.count > 0), "Should be able to set machine IDs")
252
253 print("preparing \(containerID)")
254 let (peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error) =
255 c.prepareSync(test: self, epoch: 1, machineID: machineID, bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
256 XCTAssertNil(error)
257 XCTAssertNotNil(peerID)
258 XCTAssertNotNil(permanentInfo)
259 XCTAssertNotNil(permanentInfoSig)
260 XCTAssertNotNil(stableInfo)
261 XCTAssertNotNil(stableInfoSig)
262
263 do {
264 assertNoTLKShareFor(peerID: peerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
265
266 print("\(sponsor) vouches for \(containerID)")
267 let (voucherData, voucherSig, vouchError) =
268 sponsor.vouchSync(test: self,
269 peerID: peerID!,
270 permanentInfo: permanentInfo!,
271 permanentInfoSig: permanentInfoSig!,
272 stableInfo: stableInfo!,
273 stableInfoSig: stableInfoSig!,
274 ckksKeys: [self.manateeKeySet])
275 XCTAssertNil(vouchError)
276 XCTAssertNotNil(voucherData)
277 XCTAssertNotNil(voucherSig)
278
279 // As part of the join, the sponsor should have uploaded a tlk share
280 assertTLKShareFor(peerID: peerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
281
282 print("\(containerID) joins")
283 let (joinedPeerID, _, joinError) = c.joinSync(test: self,
284 voucherData: voucherData!,
285 voucherSig: voucherSig!,
286 ckksKeys: [],
287 tlkShares: [])
288 XCTAssertNil(joinError)
289 XCTAssertEqual(joinedPeerID, peerID!)
290 }
291
292 return (c, peerID!)
293 }
294
295 func testJoin() throws {
296 let description = tmpStoreDescription(name: "container.db")
297 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
298 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
299 let containerC = try Container(name: ContainerName(container: "c", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
300
301 let machineIDs = Set(["aaa", "bbb", "ccc"])
302 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
303 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
304 XCTAssertNil(containerC.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
305
306 print("preparing A")
307 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
308 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
309 do {
310 let state = containerA.getStateSync(test: self)
311 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
312 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
313 XCTAssertNotNil(secret, "secret should not be nil")
314 XCTAssertNil(error, "error should be nil")
315 }
316 XCTAssertNil(error)
317 XCTAssertNotNil(aPeerID)
318 XCTAssertNotNil(aPermanentInfo)
319 XCTAssertNotNil(aPermanentInfoSig)
320
321 print("establishing A")
322 do {
323 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
324 XCTAssertNil(error)
325 XCTAssertNotNil(peerID)
326 }
327
328 print("preparing B")
329 let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, error2) =
330 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
331 do {
332 let state = containerB.getStateSync(test: self)
333 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
334 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
335 XCTAssertNotNil(secret, "secret should not be nil")
336 XCTAssertNil(error, "error should be nil")
337 }
338 XCTAssertNil(error2)
339 XCTAssertNotNil(bPeerID)
340 XCTAssertNotNil(bPermanentInfo)
341 XCTAssertNotNil(bPermanentInfoSig)
342
343 do {
344 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
345 print("A vouches for B, but doesn't provide any TLKShares")
346 let (_, _, errorVouchingWithoutTLKs) =
347 containerA.vouchSync(test: self,
348 peerID: bPeerID!,
349 permanentInfo: bPermanentInfo!,
350 permanentInfoSig: bPermanentInfoSig!,
351 stableInfo: bStableInfo!,
352 stableInfoSig: bStableInfoSig!,
353 ckksKeys: [])
354 XCTAssertNil(errorVouchingWithoutTLKs, "Should be no error vouching without uploading TLKShares")
355 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
356
357 print("A vouches for B, but doesn't only has provisional TLKs at the time")
358 let provisionalManateeKeySet = try self.makeFakeKeyHierarchy(zoneID: CKRecordZone.ID(zoneName: "Manatee"))
359 provisionalManateeKeySet.newUpload = true
360
361 let (_, _, errorVouchingWithProvisionalTLKs) =
362 containerA.vouchSync(test: self,
363 peerID: bPeerID!,
364 permanentInfo: bPermanentInfo!,
365 permanentInfoSig: bPermanentInfoSig!,
366 stableInfo: bStableInfo!,
367 stableInfoSig: bStableInfoSig!,
368 ckksKeys: [provisionalManateeKeySet])
369 XCTAssertNil(errorVouchingWithProvisionalTLKs, "Should be no error vouching without uploading TLKShares for a non-existent key")
370 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
371
372 print("A vouches for B")
373 let (voucherData, voucherSig, error3) =
374 containerA.vouchSync(test: self,
375 peerID: bPeerID!,
376 permanentInfo: bPermanentInfo!,
377 permanentInfoSig: bPermanentInfoSig!,
378 stableInfo: bStableInfo!,
379 stableInfoSig: bStableInfoSig!,
380 ckksKeys: [self.manateeKeySet])
381 XCTAssertNil(error3)
382 XCTAssertNotNil(voucherData)
383 XCTAssertNotNil(voucherSig)
384
385 // As part of the vouch, A should have uploaded a tlkshare for B
386 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
387
388 print("B joins")
389 let (peerID, _, error) = containerB.joinSync(test: self,
390 voucherData: voucherData!,
391 voucherSig: voucherSig!,
392 ckksKeys: [],
393 tlkShares: [])
394 XCTAssertNil(error)
395 XCTAssertEqual(peerID, bPeerID!)
396 }
397
398 _ = containerA.dumpSync(test: self)
399 _ = containerB.dumpSync(test: self)
400 _ = containerC.dumpSync(test: self)
401
402 print("preparing C")
403 let (cPeerID, cPermanentInfo, cPermanentInfoSig, cStableInfo, cStableInfoSig, error4) =
404 containerC.prepareSync(test: self, epoch: 1, machineID: "ccc", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
405 do {
406 let state = containerC.getStateSync(test: self)
407 XCTAssertFalse( state.bottles.filter { $0.peerID == cPeerID } .isEmpty, "should have a bottle for peer")
408 let secret = containerC.loadSecretSync(test: self, label: cPeerID!)
409 XCTAssertNotNil(secret, "secret should not be nil")
410 XCTAssertNil(error, "error should be nil")
411 }
412 XCTAssertNil(error4)
413 XCTAssertNotNil(cPeerID)
414 XCTAssertNotNil(cPermanentInfo)
415 XCTAssertNotNil(cPermanentInfoSig)
416
417 do {
418 // C, when it joins, will create a new CKKS zone. It should also upload TLKShares for A and B.
419 let provisionalEngramKeySet = try self.makeFakeKeyHierarchy(zoneID: CKRecordZone.ID(zoneName: "Engram"))
420 provisionalEngramKeySet.newUpload = true
421
422 assertNoTLKShareFor(peerID: cPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
423 assertNoTLKShareFor(peerID: cPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
424
425 print("B vouches for C")
426 let (voucherData, voucherSig, error) =
427 containerB.vouchSync(test: self,
428 peerID: cPeerID!,
429 permanentInfo: cPermanentInfo!,
430 permanentInfoSig: cPermanentInfoSig!,
431 stableInfo: cStableInfo!,
432 stableInfoSig: cStableInfoSig!,
433 ckksKeys: [self.manateeKeySet])
434 XCTAssertNil(error)
435 XCTAssertNotNil(voucherData)
436 XCTAssertNotNil(voucherSig)
437
438 assertTLKShareFor(peerID: cPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
439
440 print("C joins")
441 let (peerID, _, error2) = containerC.joinSync(test: self,
442 voucherData: voucherData!,
443 voucherSig: voucherSig!,
444 ckksKeys: [self.manateeKeySet, provisionalEngramKeySet],
445 tlkShares: [])
446 XCTAssertNil(error2)
447 XCTAssertEqual(peerID, cPeerID!)
448
449 assertTLKShareFor(peerID: cPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
450 assertTLKShareFor(peerID: aPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
451 assertTLKShareFor(peerID: bPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
452 }
453
454 print("A updates")
455 do {
456 let (_, error) = containerA.updateSync(test: self)
457 XCTAssertNil(error)
458 }
459
460 do {
461 let state = containerA.getStateSync(test: self)
462 let a = state.peers[aPeerID!]!
463 XCTAssertTrue(a.dynamicInfo!.includedPeerIDs.contains(cPeerID!))
464 }
465
466 _ = containerA.dumpSync(test: self)
467 _ = containerB.dumpSync(test: self)
468 _ = containerC.dumpSync(test: self)
469 }
470
471 func testJoinWithoutAllowListErrors() throws {
472 let description = tmpStoreDescription(name: "container.db")
473 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
474 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
475
476 let (peerID, permanentInfo, permanentInfoSig, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
477 do {
478 let state = containerA.getStateSync(test: self)
479 XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer")
480 let secret = containerA.loadSecretSync(test: self, label: peerID!)
481 XCTAssertNotNil(secret, "secret should not be nil")
482 XCTAssertNil(error, "error should be nil")
483 }
484 XCTAssertNil(error, "Should not have an error after preparing A")
485 XCTAssertNotNil(peerID, "Should have a peer ID after preparing A")
486 XCTAssertNotNil(permanentInfo, "Should have a permanent info after preparing A")
487 XCTAssertNotNil(permanentInfoSig, "Should have a signature after preparing A")
488
489 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa"]), "should be able to set allowed machine IDs")
490
491 let (peerID2, _, error2) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
492 XCTAssertNotNil(peerID2, "Should get a peer when you establish a now allow-listed peer")
493 XCTAssertNil(error2, "Should not get an error when you establish a now allow-listed peer")
494
495 print("preparing B")
496 let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, errorPrepareB) =
497 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
498 do {
499 let state = containerA.getStateSync(test: self)
500 XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer")
501 let secret = containerA.loadSecretSync(test: self, label: peerID!)
502 XCTAssertNotNil(secret, "secret should not be nil")
503 XCTAssertNil(error, "error should be nil")
504 }
505 XCTAssertNil(errorPrepareB, "Should not have an error after preparing B")
506 XCTAssertNotNil(bPeerID, "Should have a peer ID after preparing B")
507 XCTAssertNotNil(bPermanentInfo, "Should have a permanent info after preparing B")
508 XCTAssertNotNil(bPermanentInfoSig, "Should have a signature after preparing B")
509
510 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa"]), "should be able to set allowed machine IDs on container B")
511
512 do {
513 print("A vouches for B")
514 let (voucherData, voucherSig, error3) =
515 containerA.vouchSync(test: self,
516 peerID: bPeerID!,
517 permanentInfo: bPermanentInfo!,
518 permanentInfoSig: bPermanentInfoSig!,
519 stableInfo: bStableInfo!,
520 stableInfoSig: bStableInfoSig!,
521 ckksKeys: [])
522 XCTAssertNil(error3, "Should be no error vouching for B")
523 XCTAssertNotNil(voucherData, "Should have a voucher from A")
524 XCTAssertNotNil(voucherSig, "Should have a signature from A")
525
526 print("B joins")
527 let (peerID, _, error) = containerB.joinSync(test: self,
528 voucherData: voucherData!,
529 voucherSig: voucherSig!,
530 ckksKeys: [],
531 tlkShares: [])
532 XCTAssertNotNil(error, "Should have an error joining with an unapproved machine ID")
533 XCTAssertNil(peerID, "Should not receive a peer ID joining with an unapproved machine ID")
534 }
535 }
536
537 func testReset() throws {
538 let description = tmpStoreDescription(name: "container.db")
539 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
540
541 let machineIDs = Set(["aaa"])
542 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
543
544 print("preparing A")
545 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
546 do {
547 let state = containerA.getStateSync(test: self)
548 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
549 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
550 XCTAssertNotNil(secret, "secret should not be nil")
551 XCTAssertNil(error, "error should be nil")
552 }
553 XCTAssertNil(error)
554 XCTAssertNotNil(aPeerID)
555 XCTAssertNotNil(aPermanentInfo)
556 XCTAssertNotNil(aPermanentInfoSig)
557
558 print("establishing A")
559 do {
560 assertNoTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
561 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
562 XCTAssertNil(error)
563 XCTAssertNotNil(peerID)
564 assertTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
565 }
566
567 print("reset A")
568 do {
569 let error = containerA.resetSync(resetReason: .testGenerated, test: self)
570 XCTAssertNil(error)
571 }
572 do {
573 let (dict, error) = containerA.dumpSync(test: self)
574 XCTAssertNil(error)
575 XCTAssertNotNil(dict)
576 let peers: Array<Any> = dict!["peers"] as! Array<Any>
577 XCTAssertEqual(0, peers.count)
578 }
579 }
580
581 func testResetLocal() throws {
582 let description = tmpStoreDescription(name: "container.db")
583 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
584
585 let machineIDs = Set(["aaa"])
586 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
587
588 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
589 do {
590 let state = containerA.getStateSync(test: self)
591 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
592 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
593 XCTAssertNotNil(secret, "secret should not be nil")
594 XCTAssertNil(error, "error should be nil")
595 }
596 XCTAssertNil(error, "Should be no error preparing an identity")
597 XCTAssertNotNil(aPeerID, "Should have a peer ID after preparing")
598 XCTAssertNotNil(aPermanentInfo, "Should have a permanentInfo after preparing")
599 XCTAssertNotNil(aPermanentInfoSig, "Should have a permanentInfoSign after preparing")
600
601 do {
602 let (dict, error) = containerA.dumpSync(test: self)
603 XCTAssertNil(error, "Should be no error dumping")
604 XCTAssertNotNil(dict, "Should receive a dump dictionary")
605
606 let selfInfo: [AnyHashable: Any]? = dict!["self"] as! [AnyHashable: Any]?
607 XCTAssertNotNil(selfInfo, "Should have a self dictionary")
608
609 let selfPeer: String? = selfInfo!["peerID"] as! String?
610 XCTAssertNotNil(selfPeer, "self peer should be part of the dump")
611 }
612
613 do {
614 let error = containerA.localResetSync(test: self)
615 XCTAssertNil(error, "local-reset shouldn't error")
616 }
617 do {
618 let (dict, error) = containerA.dumpSync(test: self)
619
620 XCTAssertNil(error, "Should be no error dumping")
621 XCTAssertNotNil(dict, "Should receive a dump dictionary")
622
623 let selfInfo: [AnyHashable: Any]? = dict!["self"] as! [AnyHashable: Any]?
624 XCTAssertNotNil(selfInfo, "Should have a self dictionary")
625
626 let selfPeer: String? = selfInfo!["peerID"] as! String?
627 XCTAssertNil(selfPeer, "self peer should not be part of the dump")
628 }
629 }
630
631 func testReplayAttack() throws {
632 let description = tmpStoreDescription(name: "container.db")
633 var containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
634 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
635 let containerC = try Container(name: ContainerName(container: "c", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
636
637 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: Set(["aaa", "bbb", "ccc"])))
638 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: Set(["aaa", "bbb", "ccc"])))
639 XCTAssertNil(containerC.setAllowedMachineIDsSync(test: self, allowedMachineIDs: Set(["aaa", "bbb", "ccc"])))
640
641 print("preparing")
642 let (peerID, _, _, _, _, _) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
643 do {
644 let state = containerA.getStateSync(test: self)
645 XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer")
646 let secret = containerA.loadSecretSync(test: self, label: peerID!)
647 XCTAssertNotNil(secret, "secret should not be nil")
648 }
649 let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, _) = containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
650 do {
651 let state = containerB.getStateSync(test: self)
652 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
653 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
654 XCTAssertNotNil(secret, "secret should not be nil")
655 }
656 let (cPeerID, cPermanentInfo, cPermanentInfoSig, cStableInfo, cStableInfoSig, _) = containerC.prepareSync(test: self, epoch: 1, machineID: "ccc", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
657 do {
658 let state = containerC.getStateSync(test: self)
659 XCTAssertFalse( state.bottles.filter { $0.peerID == cPeerID } .isEmpty, "should have a bottle for peer")
660 let secret = containerC.loadSecretSync(test: self, label: cPeerID!)
661 XCTAssertNotNil(secret, "secret should not be nil")
662 }
663 print("establishing A")
664 _ = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
665
666 do {
667 print("A vouches for B")
668 let (voucherData, voucherSig, _) = containerA.vouchSync(test: self,
669 peerID: bPeerID!,
670 permanentInfo: bPermanentInfo!,
671 permanentInfoSig: bPermanentInfoSig!,
672 stableInfo: bStableInfo!,
673 stableInfoSig: bStableInfoSig!,
674 ckksKeys: [])
675
676 print("B joins")
677 _ = containerB.joinSync(test: self,
678 voucherData: voucherData!,
679 voucherSig: voucherSig!,
680 ckksKeys: [],
681 tlkShares: [])
682 }
683
684 print("A updates")
685 _ = containerA.updateSync(test: self)
686 let earlyClock: TPCounter
687 do {
688 let state = containerA.getStateSync(test: self)
689 let b = state.peers[bPeerID!]!
690 earlyClock = b.dynamicInfo!.clock
691 }
692
693 // Take a snapshot
694 let snapshot = cuttlefish.state
695
696 do {
697 print("B vouches for C")
698 let (voucherData, voucherSig, _) = containerB.vouchSync(test: self, peerID: cPeerID!,
699 permanentInfo: cPermanentInfo!,
700 permanentInfoSig: cPermanentInfoSig!,
701 stableInfo: cStableInfo!,
702 stableInfoSig: cStableInfoSig!,
703 ckksKeys: [])
704
705 print("C joins")
706 _ = containerC.joinSync(test: self,
707 voucherData: voucherData!,
708 voucherSig: voucherSig!,
709 ckksKeys: [],
710 tlkShares: [])
711 }
712
713 print("B updates")
714 _ = containerB.updateSync(test: self)
715
716 print("A updates")
717 _ = containerA.updateSync(test: self)
718 let lateClock: TPCounter
719 do {
720 let state = containerA.getStateSync(test: self)
721 let b = state.peers[bPeerID!]!
722 lateClock = b.dynamicInfo!.clock
723 XCTAssertTrue(earlyClock < lateClock)
724 }
725
726 print("Reverting cuttlefish to the snapshot")
727 cuttlefish.state = snapshot
728 cuttlefish.makeSnapshot()
729
730 print("A updates, fetching the old snapshot from cuttlefish")
731 _ = containerA.updateSync(test: self)
732
733 print("Reload A. Now we see whether it persisted the replayed snapshot in the previous step.")
734 containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
735 do {
736 let state = containerA.getStateSync(test: self)
737 let b = state.peers[bPeerID!]!
738 XCTAssertEqual(lateClock, b.dynamicInfo!.clock)
739 }
740 }
741
742 // TODO: need a real configurable mock cuttlefish
743 func testFetchPolicyDocuments() throws {
744
745 // 1 is known locally via builtin, 3 is not but is known to cuttlefish
746 let policies =
747 [
748 1: ("SHA256:TLXrcQmY4ue3oP5pCX1pwsi9BF8cKfohlJBilCroeBs=",
749 "CAESDgoGaVBob25lEgRmdWxsEgwKBGlQYWQSBGZ1bGwSCwoDTWFjEgRmdWxsEgwKBGlNYWMSBGZ1bGwSDQoHQXBwbGVUVhICdHYS" +
750 "DgoFV2F0Y2gSBXdhdGNoGhEKCVBDU0VzY3JvdxIEZnVsbBoXCgRXaUZpEgRmdWxsEgJ0dhIFd2F0Y2gaGQoRU2FmYXJpQ3JlZGl0" +
751 "Q2FyZHMSBGZ1bGwiDAoEZnVsbBIEZnVsbCIUCgV3YXRjaBIEZnVsbBIFd2F0Y2giDgoCdHYSBGZ1bGwSAnR2"),
752 3: ("SHA256:JZzazSuHXrUhiOfSgElsg6vYKpnvvEPVpciR8FewRWg=",
753 "CAMSDgoGaVBob25lEgRmdWxsEgwKBGlQYWQSBGZ1bGwSCwoDTWFjEgRmdWxsEgwKBGlNYWMSBGZ1bGwSDQoHQXBwbGVUVhICdHYSDgoFV2F0Y2gSBXdhdGNoEhcKDkF1ZGlvQWNjZXNzb3J5EgVhdWRpbxocCg1EZXZpY2VQYWlyaW5nEgRmdWxsEgV3YXRjaBoXCghBcHBsZVBheRIEZnVsbBIFd2F0Y2gaJAoVUHJvdGVjdGVkQ2xvdWRTdG9yYWdlEgRmdWxsEgV3YXRjaBoXCghCYWNrc3RvcBIEZnVsbBIFd2F0Y2gaGQoKQXV0b1VubG9jaxIEZnVsbBIFd2F0Y2gaHwoQU2VjdXJlT2JqZWN0U3luYxIEZnVsbBIFd2F0Y2gaIAoRU2FmYXJpQ3JlZGl0Q2FyZHMSBGZ1bGwSBXdhdGNoGhMKBEhvbWUSBGZ1bGwSBXdhdGNoGh4KD1NhZmFyaVBhc3N3b3JkcxIEZnVsbBIFd2F0Y2gaGwoMQXBwbGljYXRpb25zEgRmdWxsEgV3YXRjaBoVCgZFbmdyYW0SBGZ1bGwSBXdhdGNoGi0KE0xpbWl0ZWRQZWVyc0FsbG93ZWQSBGZ1bGwSBXdhdGNoEgJ0dhIFYXVkaW8aFgoHTWFuYXRlZRIEZnVsbBIFd2F0Y2gaHgoEV2lGaRIEZnVsbBIFd2F0Y2gSAnR2EgVhdWRpbxoVCgZIZWFsdGgSBGZ1bGwSBXdhdGNoIhMKBGZ1bGwSBGZ1bGwSBXdhdGNoIhsKBWF1ZGlvEgRmdWxsEgV3YXRjaBIFYXVkaW8iFAoFd2F0Y2gSBGZ1bGwSBXdhdGNoIhUKAnR2EgRmdWxsEgV3YXRjaBICdHYyIgoWAAQiEgIEdndodAoKXkFwcGxlUGF5JBIIQXBwbGVQYXkyJgoYAAQiFAIEdndodAoMXkF1dG9VbmxvY2skEgpBdXRvVW5sb2NrMh4KFAAEIhACBHZ3aHQKCF5FbmdyYW0kEgZFbmdyYW0yHgoUAAQiEAIEdndodAoIXkhlYWx0aCQSBkhlYWx0aDIaChIABCIOAgR2d2h0CgZeSG9tZSQSBEhvbWUyIAoVAAQiEQIEdndodAoJXk1hbmF0ZWUkEgdNYW5hdGVlMjgKIQAEIh0CBHZ3aHQKFV5MaW1pdGVkUGVlcnNBbGxvd2VkJBITTGltaXRlZFBlZXJzQWxsb3dlZDJdClAAAhIeAAQiGgIEdndodAoSXkNvbnRpbnVpdHlVbmxvY2skEhUABCIRAgR2d2h0CgleSG9tZUtpdCQSFQAEIhECBHZ3aHQKCV5BcHBsZVRWJBIJTm90U3luY2VkMisKGwAEIhcCBGFncnAKD15bMC05QS1aXXsxMH1cLhIMQXBwbGljYXRpb25zMsUBCrABAAISNAABChMABCIPAgVjbGFzcwoGXmdlbnAkChsABCIXAgRhZ3JwCg9eY29tLmFwcGxlLnNiZCQSPQABChMABCIPAgVjbGFzcwoGXmtleXMkCiQABCIgAgRhZ3JwChheY29tLmFwcGxlLnNlY3VyaXR5LnNvcyQSGQAEIhUCBHZ3aHQKDV5CYWNrdXBCYWdWMCQSHAAEIhgCBHZ3aHQKEF5pQ2xvdWRJZGVudGl0eSQSEFNlY3VyZU9iamVjdFN5bmMyYwpbAAISEgAEIg4CBHZ3aHQKBl5XaUZpJBJDAAEKEwAEIg8CBWNsYXNzCgZeZ2VucCQKEwAEIg8CBGFncnAKB15hcHBsZSQKFQAEIhECBHN2Y2UKCV5BaXJQb3J0JBIEV2lGaTLbAgrBAgACEhkABCIVAgR2d2h0Cg1eUENTQ2xvdWRLaXQkEhcABCITAgR2d2h0CgteUENTRXNjcm93JBIUAAQiEAIEdndodAoIXlBDU0ZERSQSGQAEIhUCBHZ3aHQKDV5QQ1NGZWxkc3BhciQSGQAEIhUCBHZ3aHQKDV5QQ1NNYWlsRHJvcCQSGgAEIhYCBHZ3aHQKDl5QQ1NNYXN0ZXJLZXkkEhYABCISAgR2d2h0CgpeUENTTm90ZXMkEhcABCITAgR2d2h0CgteUENTUGhvdG9zJBIYAAQiFAIEdndodAoMXlBDU1NoYXJpbmckEh0ABCIZAgR2d2h0ChFeUENTaUNsb3VkQmFja3VwJBIcAAQiGAIEdndodAoQXlBDU2lDbG91ZERyaXZlJBIZAAQiFQIEdndodAoNXlBDU2lNZXNzYWdlJBIVUHJvdGVjdGVkQ2xvdWRTdG9yYWdlMkAKKwAEIicCBGFncnAKH15jb20uYXBwbGUuc2FmYXJpLmNyZWRpdC1jYXJkcyQSEVNhZmFyaUNyZWRpdENhcmRzMjQKIQAEIh0CBGFncnAKFV5jb20uYXBwbGUuY2ZuZXR3b3JrJBIPU2FmYXJpUGFzc3dvcmRzMm0KXAACEh4ABCIaAgR2d2h0ChJeQWNjZXNzb3J5UGFpcmluZyQSGgAEIhYCBHZ3aHQKDl5OYW5vUmVnaXN0cnkkEhwABCIYAgR2d2h0ChBeV2F0Y2hNaWdyYXRpb24kEg1EZXZpY2VQYWlyaW5nMi0KIQAEIh0CBGFncnAKFV5jb20uYXBwbGUuY2ZuZXR3b3JrJBIIQmFja3N0b3A="),
754 ]
755 let (request1, data1) = policies[1]!
756 let (request3, data3) = policies[3]!
757
758 let description = tmpStoreDescription(name: "container.db")
759 let container = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
760
761 // nothing
762 let (response1, error1) = container.fetchPolicyDocumentsSync(test: self, keys: [:])
763 XCTAssertNil(error1, "No error querying for an empty list")
764 XCTAssertEqual(response1, [:], "Received empty dictionary")
765
766 // local stuff
767 let (response2, error2) = container.fetchPolicyDocumentsSync(test: self, keys: [1: request1])
768 XCTAssertNil(error2, "No error getting locally known policy document")
769 XCTAssertEqual(response2?.count, 1, "Got one response for request for one locally known policy")
770 XCTAssertEqual(response2?[1]?[0], request1, "retrieved hash matches request hash")
771 XCTAssertEqual(response2?[1]?[1], data1, "retrieved data matches known data")
772
773 // fetch remote
774 let (response3, error3) = container.fetchPolicyDocumentsSync(test: self, keys: [1: request1, 3: request3])
775 XCTAssertNil(error3, "No error fetching local + remote policy")
776 XCTAssertEqual(response3?.count, 2, "Got two responses for local+remote policy request")
777 XCTAssertEqual(response3?[1]?[0], request1, "retrieved hash matches local request hash")
778 XCTAssertEqual(response3?[1]?[1], data1, "retrieved data matches local known data")
779 XCTAssertEqual(response3?[3]?[0], request3, "retrieved hash matches remote request hash")
780 XCTAssertEqual(response3?[3]?[1], data3, "retrieved data matches remote known data")
781
782 // invalid version
783 let (response4, error4) = container.fetchPolicyDocumentsSync(test: self, keys: [9000: "not a hash"])
784 XCTAssertNil(response4, "No response for wrong [version: hash] combination")
785 XCTAssertNotNil(error4, "Expected error fetching invalid policy version")
786
787 // valid + invalid
788 let (response5, error5) = container.fetchPolicyDocumentsSync(test: self, keys: [9000: "not a hash",
789 1: request1,
790 3: request3, ])
791 XCTAssertNil(response5, "No response for valid + unknown [version: hash] combination")
792 XCTAssertNotNil(error5, "Expected error fetching valid + invalid policy version")
793 }
794
795 func testEscrowKeys() throws {
796
797 XCTAssertThrowsError(try EscrowKeys.retrieveEscrowKeysFromKeychain(label: "hash"), "retrieveEscrowKeysFromKeychain should throw error")
798 XCTAssertThrowsError(try EscrowKeys.findEscrowKeysForLabel(label: "hash"), "findEscrowKeysForLabel should throw error")
799
800 let secretString = "i'm a secret!"
801 XCTAssertNotNil(secretString, "secretString should not be nil")
802
803 let secretData: Data? = secretString.data(using: .utf8)
804 XCTAssertNotNil(secretData, "secretData should not be nil")
805
806 let keys = try EscrowKeys(secret: secretData!, bottleSalt: "123456789")
807 XCTAssertNotNil(keys, "keys should not be nil")
808
809 XCTAssertNotNil(keys.secret, "secret should not be nil")
810 XCTAssertNotNil(keys.bottleSalt, "bottleSalt should not be nil")
811 XCTAssertNotNil(keys.encryptionKey, "encryptionKey should not be nil")
812 XCTAssertNotNil(keys.signingKey, "signingKey should not be nil")
813 XCTAssertNotNil(keys.symmetricKey, "symmetricKey should not be nil")
814
815 let hash = try EscrowKeys.hashEscrowedSigningPublicKey(keyData: keys.signingKey.publicKey().spki())
816 XCTAssertNotNil(hash, "hash should not be nil")
817
818 let result = try EscrowKeys.storeEscrowedSigningKeyPair(keyData: keys.signingKey.keyData, label: "Signing Key")
819 XCTAssertTrue(result, "result should be true")
820
821 let escrowKey = try EscrowKeys.retrieveEscrowKeysFromKeychain(label: hash)
822 XCTAssertNotNil(escrowKey, "escrowKey should not be nil")
823
824 let (signingKey, encryptionKey, symmetricKey) = try EscrowKeys.findEscrowKeysForLabel(label: hash)
825 XCTAssertNotNil(signingKey, "signingKey should not be nil")
826 XCTAssertNotNil(encryptionKey, "encryptionKey should not be nil")
827 XCTAssertNotNil(symmetricKey, "symmetricKey should not be nil")
828 }
829
830 func testEscrowKeyTestVectors() {
831
832 let secretString = "I'm a secretI'm a secretI'm a secretI'm a secretI'm a secretI'm a secret"
833
834 let secret = secretString.data(using: .utf8)
835
836 do {
837 let testv1 = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeySigning, masterSecret: secret!, bottleSalt: testDSID)
838 XCTAssertEqual(testv1, signingKey_384, "signing keys should match")
839
840 let testv2 = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeyEncryption, masterSecret: secret!, bottleSalt: testDSID)
841 XCTAssertEqual(testv2, encryptionKey_384, "encryption keys should match")
842
843 let testv3 = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeySymmetric, masterSecret: secret!, bottleSalt: testDSID)
844 XCTAssertEqual(testv3, symmetricKey_384, "symmetric keys should match")
845
846 let newSecretString = "I'm f secretI'm a secretI'm a secretI'm a secretI'm a secretI'm a secret"
847 let newSecret = newSecretString.data(using: .utf8)
848
849 let testv4 = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeySigning, masterSecret: newSecret!, bottleSalt: testDSID)
850 XCTAssertNotEqual(testv4, signingKey_384, "signing keys should not match")
851
852 let testv5 = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeyEncryption, masterSecret: newSecret!, bottleSalt: testDSID)
853 XCTAssertNotEqual(testv5, encryptionKey_384, "encryption keys should not match")
854
855 let testv6 = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeySymmetric, masterSecret: newSecret!, bottleSalt: testDSID)
856 XCTAssertNotEqual(testv6, symmetricKey_384, "symmetric keys should not match")
857 } catch {
858 XCTFail("error testing escrow key test vectors \(error)")
859 }
860 }
861
862 func testRecoveryKeyTestVectors() {
863 let secretString = "I'm a secretI'm a secretI'm a secretI'm a secretI'm a secretI'm a secret"
864
865 let secret = secretString.data(using: .utf8)
866
867 do {
868 let testv1 = try RecoveryKeySet.generateRecoveryKey(keyType: recoveryKeyType.kOTRecoveryKeySigning, masterSecret: secret!, recoverySalt: testDSID)
869 XCTAssertEqual(testv1, recovery_signingKey_384, "signing keys should match")
870
871 let testv2 = try RecoveryKeySet.generateRecoveryKey(keyType: recoveryKeyType.kOTRecoveryKeyEncryption, masterSecret: secret!, recoverySalt: testDSID)
872 XCTAssertEqual(testv2, recovery_encryptionKey_384, "encryption keys should match")
873
874 let newSecretString = "I'm f secretI'm a secretI'm a secretI'm a secretI'm a secretI'm a secret"
875 let newSecret = newSecretString.data(using: .utf8)
876
877 let testv4 = try RecoveryKeySet.generateRecoveryKey(keyType: recoveryKeyType.kOTRecoveryKeySigning, masterSecret: newSecret!, recoverySalt: testDSID)
878 XCTAssertNotEqual(testv4, recovery_signingKey_384, "signing keys should not match")
879
880 let testv5 = try RecoveryKeySet.generateRecoveryKey(keyType: recoveryKeyType.kOTRecoveryKeyEncryption, masterSecret: newSecret!, recoverySalt: testDSID)
881 XCTAssertNotEqual(testv5, recovery_encryptionKey_384, "encryption keys should not match")
882 } catch {
883 XCTFail("error testing RecoveryKey test vectors \(error)")
884 }
885 }
886
887 func testJoiningWithBottle() throws {
888 var bottleA: BottleMO
889 var entropy: Data
890 let description = tmpStoreDescription(name: "container.db")
891 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
892 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
893
894 let machineIDs = Set(["aaa", "bbb"])
895 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
896 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
897
898 print("preparing A")
899 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
900 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
901 do {
902 var state = containerA.getStateSync(test: self)
903 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
904
905 bottleA = state.bottles.removeFirst()
906
907 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
908 XCTAssertNotNil(secret, "secret should not be nil")
909 XCTAssertNil(error, "error should be nil")
910 }
911 XCTAssertNotNil(aPeerID)
912 XCTAssertNotNil(aPermanentInfo)
913 XCTAssertNotNil(aPermanentInfoSig)
914
915 print("establishing A")
916 do {
917 assertNoTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
918 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
919 XCTAssertNil(error)
920 XCTAssertNotNil(peerID)
921 assertTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
922 }
923 do {
924 let state = containerA.getStateSync(test: self)
925 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
926 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
927 entropy = secret!
928 XCTAssertNotNil(secret, "secret should not be nil")
929 XCTAssertNil(error, "error should be nil")
930 }
931
932 _ = containerB.updateSync(test: self)
933
934 print("preparing B")
935 let (bPeerID, _, _, _, _, error2) =
936 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
937 do {
938 let state = containerB.getStateSync(test: self)
939 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
940 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
941 XCTAssertNotNil(secret, "secret should not be nil")
942 XCTAssertNil(error, "error should be nil")
943 }
944 XCTAssertNil(error2)
945
946 do {
947 print("B prepares to join via bottle")
948
949 let (bottlePeerID, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!)
950 XCTAssertNil(errorPreflight, "Should be no error preflighting a vouch with bottle")
951 XCTAssertEqual(bottlePeerID, aPeerID, "Bottle should be for peer A")
952
953 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
954
955 XCTAssertNil(error3)
956 XCTAssertNotNil(voucherData)
957 XCTAssertNotNil(voucherSig)
958
959 // Before B joins, there should be no TLKShares for B
960 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
961
962 print("B joins")
963 let (peerID, _, error) = containerB.joinSync(test: self, voucherData: voucherData!, voucherSig: voucherSig!, ckksKeys: [self.manateeKeySet], tlkShares: [])
964 XCTAssertNil(error)
965 XCTAssertEqual(peerID, bPeerID!)
966
967 // But afterward, it has one!
968 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
969 }
970 }
971
972 func testJoiningWithBottleAndEmptyBottleSalt() throws {
973 var bottleA: BottleMO
974 var entropy: Data
975 let description = tmpStoreDescription(name: "container.db")
976 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
977 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
978
979 let machineIDs = Set(["aaa", "bbb"])
980 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
981 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
982
983 print("preparing A")
984 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
985 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "", bottleID: UUID().uuidString, modelID: "iPhone1,1")
986 do {
987 var state = containerA.getStateSync(test: self)
988 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
989
990 bottleA = state.bottles.removeFirst()
991
992 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
993 XCTAssertNotNil(secret, "secret should not be nil")
994 XCTAssertNil(error, "error should be nil")
995 }
996 XCTAssertNotNil(aPeerID)
997 XCTAssertNotNil(aPermanentInfo)
998 XCTAssertNotNil(aPermanentInfoSig)
999
1000 print("establishing A")
1001 do {
1002 assertNoTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1003 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
1004 XCTAssertNil(error)
1005 XCTAssertNotNil(peerID)
1006 assertTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1007 }
1008 do {
1009 let state = containerA.getStateSync(test: self)
1010 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1011 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1012 entropy = secret!
1013 XCTAssertNotNil(secret, "secret should not be nil")
1014 XCTAssertNil(error, "error should be nil")
1015 }
1016
1017 _ = containerB.updateSync(test: self)
1018
1019 print("preparing B")
1020 let (bPeerID, _, _, _, _, error2) =
1021 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1022 do {
1023 let state = containerB.getStateSync(test: self)
1024 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1025 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1026 XCTAssertNotNil(secret, "secret should not be nil")
1027 XCTAssertNil(error, "error should be nil")
1028 }
1029 XCTAssertNil(error2)
1030
1031 do {
1032 print("B prepares to join via bottle")
1033
1034 let (bottlePeerID, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!)
1035 XCTAssertNil(errorPreflight, "Should be no error preflighting a vouch with bottle")
1036 XCTAssertEqual(bottlePeerID, aPeerID, "Bottle should be for peer A")
1037
1038 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1039
1040 XCTAssertNil(error3)
1041 XCTAssertNotNil(voucherData)
1042 XCTAssertNotNil(voucherSig)
1043
1044 // Before B joins, there should be no TLKShares for B
1045 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1046
1047 print("B joins")
1048 let (peerID, _, error) = containerB.joinSync(test: self, voucherData: voucherData!, voucherSig: voucherSig!, ckksKeys: [self.manateeKeySet], tlkShares: [])
1049 XCTAssertNil(error)
1050 XCTAssertEqual(peerID, bPeerID!)
1051
1052 // But afterward, it has one!
1053 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1054 }
1055 }
1056
1057 func testJoiningWithWrongEscrowRecordForBottle() throws {
1058 var entropy: Data
1059 let description = tmpStoreDescription(name: "container.db")
1060 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1061 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1062
1063 let machineIDs = Set(["aaa", "bbb"])
1064 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1065 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1066
1067 print("preparing A")
1068 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1069 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1070 do {
1071 let state = containerA.getStateSync(test: self)
1072 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1073 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1074 XCTAssertNotNil(secret, "secret should not be nil")
1075 XCTAssertNil(error, "error should be nil")
1076 }
1077 XCTAssertNil(error)
1078 XCTAssertNotNil(aPeerID)
1079 XCTAssertNotNil(aPermanentInfo)
1080 XCTAssertNotNil(aPermanentInfoSig)
1081
1082 print("establishing A")
1083 do {
1084 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
1085 XCTAssertNil(error)
1086 XCTAssertNotNil(peerID)
1087 }
1088 do {
1089 let state = containerA.getStateSync(test: self)
1090 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1091 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1092 entropy = secret!
1093 XCTAssertNotNil(secret, "secret should not be nil")
1094 XCTAssertNil(error, "error should be nil")
1095 }
1096
1097 _ = containerB.updateSync(test: self)
1098
1099 print("preparing B")
1100 let (bPeerID, _, _, _, _, error2) =
1101 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1102 do {
1103 let state = containerB.getStateSync(test: self)
1104 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1105 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1106 XCTAssertNotNil(secret, "secret should not be nil")
1107 XCTAssertNil(error, "error should be nil")
1108 }
1109 XCTAssertNil(error2)
1110
1111 do {
1112 print("B joins via bottle")
1113
1114 let (bottlePeerID, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: "wrong escrow record")
1115 XCTAssertNotNil(errorPreflight, "Should be an error preflighting bottle that doesn't exist")
1116 XCTAssertNil(bottlePeerID, "peerID should be nil for no bottle")
1117
1118 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: "wrong escrow record", entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1119
1120 XCTAssertNotNil(error3)
1121 XCTAssertNil(voucherData)
1122 XCTAssertNil(voucherSig)
1123 }
1124 }
1125
1126 func testJoiningWithWrongBottle() throws {
1127 var bottleB: BottleMO
1128 var entropy: Data
1129 let description = tmpStoreDescription(name: "container.db")
1130 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1131 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1132
1133 let machineIDs = Set(["aaa", "bbb"])
1134 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1135 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1136
1137 print("preparing A")
1138 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1139 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1140 do {
1141 let state = containerA.getStateSync(test: self)
1142 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1143 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1144 XCTAssertNotNil(secret, "secret should not be nil")
1145 XCTAssertNil(error, "error should be nil")
1146 }
1147 XCTAssertNil(error)
1148 XCTAssertNotNil(aPeerID)
1149 XCTAssertNotNil(aPermanentInfo)
1150 XCTAssertNotNil(aPermanentInfoSig)
1151
1152 print("establishing A")
1153 do {
1154 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
1155 XCTAssertNil(error)
1156 XCTAssertNotNil(peerID)
1157 }
1158 do {
1159 let state = containerA.getStateSync(test: self)
1160 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1161 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1162 entropy = secret!
1163 XCTAssertNotNil(secret, "secret should not be nil")
1164 XCTAssertNil(error, "error should be nil")
1165 }
1166
1167 print("preparing B")
1168 let (bPeerID, _, _, _, _, error2) =
1169 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1170 do {
1171 var state = containerB.getStateSync(test: self)
1172 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1173 bottleB = state.bottles.removeFirst()
1174 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1175 XCTAssertNotNil(secret, "secret should not be nil")
1176 XCTAssertNil(error, "error should be nil")
1177 }
1178 XCTAssertNil(error2)
1179
1180 do {
1181 print("B joins via bottle")
1182
1183 let (bottlePeerID, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleB.bottleID!)
1184 XCTAssertNil(errorPreflight, "Should be no error preflighting a vouch with bottle")
1185 XCTAssertEqual(bottlePeerID, bPeerID, "Bottle should be for peer B")
1186
1187 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleB.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1188
1189 XCTAssertNotNil(error3)
1190 XCTAssertNil(voucherData)
1191 XCTAssertNil(voucherSig)
1192 }
1193 }
1194
1195 func testJoiningWithBottleAndBadSalt() throws {
1196 var bottleA: BottleMO
1197 var entropy: Data
1198 let description = tmpStoreDescription(name: "container.db")
1199 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1200 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1201
1202 let machineIDs = Set(["aaa", "bbb"])
1203 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1204 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1205
1206 print("preparing A")
1207 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1208 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1209 do {
1210 var state = containerA.getStateSync(test: self)
1211 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1212 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1213 bottleA = state.bottles.removeFirst()
1214 XCTAssertNotNil(secret, "secret should not be nil")
1215 XCTAssertNil(error, "error should be nil")
1216 }
1217 XCTAssertNil(error)
1218 XCTAssertNotNil(aPeerID)
1219 XCTAssertNotNil(aPermanentInfo)
1220 XCTAssertNotNil(aPermanentInfoSig)
1221
1222 print("establishing A")
1223 do {
1224 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
1225 XCTAssertNil(error)
1226 XCTAssertNotNil(peerID)
1227 }
1228 do {
1229 let state = containerA.getStateSync(test: self)
1230 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1231 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1232 entropy = secret!
1233 XCTAssertNotNil(secret, "secret should not be nil")
1234 XCTAssertNil(error, "error should be nil")
1235 }
1236
1237 _ = containerB.updateSync(test: self)
1238
1239 print("preparing B")
1240 let (bPeerID, _, _, _, _, error2) =
1241 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1242 do {
1243 let state = containerB.getStateSync(test: self)
1244 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1245 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1246 XCTAssertNotNil(secret, "secret should not be nil")
1247 XCTAssertNil(error, "error should be nil")
1248 }
1249 XCTAssertNil(error2)
1250
1251 do {
1252 print("B joins via bottle")
1253
1254 let (bottlePeerID, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!)
1255 XCTAssertNil(errorPreflight, "Should be no error preflighting a vouch with bottle")
1256 XCTAssertEqual(bottlePeerID, aPeerID, "Bottle should be for peer A")
1257
1258 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "987654321", tlkShares: [])
1259
1260 XCTAssertNotNil(error3)
1261 XCTAssertNil(voucherData)
1262 XCTAssertNil(voucherSig)
1263 }
1264 }
1265
1266 func testJoiningWithBottleAndBadSecret() throws {
1267 var bottleA: BottleMO
1268 let description = tmpStoreDescription(name: "container.db")
1269 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1270 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1271
1272 let machineIDs = Set(["aaa", "bbb"])
1273 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1274 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1275
1276 print("preparing A")
1277 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1278 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1279 do {
1280 var state = containerA.getStateSync(test: self)
1281 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1282 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1283 bottleA = state.bottles.removeFirst()
1284 XCTAssertNotNil(secret, "secret should not be nil")
1285 XCTAssertNil(error, "error should be nil")
1286 }
1287 XCTAssertNil(error)
1288 XCTAssertNotNil(aPeerID)
1289 XCTAssertNotNil(aPermanentInfo)
1290 XCTAssertNotNil(aPermanentInfoSig)
1291
1292 print("establishing A")
1293 do {
1294 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
1295 XCTAssertNil(error)
1296 XCTAssertNotNil(peerID)
1297 }
1298 do {
1299 let state = containerA.getStateSync(test: self)
1300 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1301 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1302 XCTAssertNotNil(secret, "secret should not be nil")
1303 XCTAssertNil(error, "error should be nil")
1304 }
1305
1306 _ = containerB.updateSync(test: self)
1307
1308 print("preparing B")
1309 let (bPeerID, _, _, _, _, error2) =
1310 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1311 do {
1312 let state = containerB.getStateSync(test: self)
1313 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1314 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1315 XCTAssertNotNil(secret, "secret should not be nil")
1316 XCTAssertNil(error, "error should be nil")
1317 }
1318 XCTAssertNil(error2)
1319
1320 do {
1321 print("B joins via bottle")
1322
1323 let (bottlePeerID, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!)
1324 XCTAssertNil(errorPreflight, "Should be no error preflighting a vouch with bottle")
1325 XCTAssertEqual(bottlePeerID, aPeerID, "Bottle should be for peer A")
1326
1327 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: Data(count: Int(OTMasterSecretLength)), bottleSalt: "123456789", tlkShares: [])
1328
1329 XCTAssertNotNil(error3)
1330 XCTAssertNil(voucherData)
1331 XCTAssertNil(voucherSig)
1332 }
1333 }
1334
1335 func testJoiningWithNoFetchAllBottles() throws {
1336 var bottleA: BottleMO
1337 var entropy: Data
1338 let description = tmpStoreDescription(name: "container.db")
1339 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1340 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1341
1342 let machineIDs = Set(["aaa", "bbb"])
1343 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1344 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1345
1346 print("preparing A")
1347 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1348 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1349 do {
1350 var state = containerA.getStateSync(test: self)
1351 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1352 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1353 entropy = secret!
1354 bottleA = state.bottles.removeFirst()
1355 XCTAssertNotNil(secret, "secret should not be nil")
1356 XCTAssertNil(error, "error should be nil")
1357 }
1358 XCTAssertNil(error)
1359 XCTAssertNotNil(aPeerID)
1360 XCTAssertNotNil(aPermanentInfo)
1361 XCTAssertNotNil(aPermanentInfoSig)
1362
1363 print("establishing A")
1364 do {
1365 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
1366 XCTAssertNil(error)
1367 XCTAssertNotNil(peerID)
1368 }
1369 do {
1370 let state = containerA.getStateSync(test: self)
1371 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1372 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1373 XCTAssertNotNil(secret, "secret should not be nil")
1374 XCTAssertNil(error, "error should be nil")
1375 }
1376
1377 print("preparing B")
1378 let (bPeerID, _, _, _, _, error2) =
1379 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1380 do {
1381 let state = containerB.getStateSync(test: self)
1382 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1383 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1384 XCTAssertNotNil(secret, "secret should not be nil")
1385 XCTAssertNil(error, "error should be nil")
1386 }
1387 XCTAssertNil(error2)
1388
1389 do {
1390 print("B joins via bottle")
1391
1392 self.cuttlefish.fetchViableBottlesError.append(FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .changeTokenExpired))
1393
1394 let (bottlePeerID, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!)
1395 XCTAssertNotNil(errorPreflight, "Should be an error preflighting a vouch with bottle with a fetch error")
1396 XCTAssertNil(bottlePeerID, "peerID should be nil")
1397
1398 self.cuttlefish.fetchViableBottlesError.append(FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .changeTokenExpired))
1399
1400 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1401
1402 XCTAssertNotNil(error3)
1403 XCTAssertNil(voucherData)
1404 XCTAssertNil(voucherSig)
1405 }
1406 }
1407
1408 func testJoinByPreapproval() throws {
1409 let description = tmpStoreDescription(name: "container.db")
1410 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1411 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1412
1413 let machineIDs = Set(["aaa", "bbb"])
1414 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1415 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1416
1417 print("preparing A")
1418 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1419 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1420 XCTAssertNil(error)
1421 XCTAssertNotNil(aPeerID)
1422 XCTAssertNotNil(aPermanentInfo)
1423 XCTAssertNotNil(aPermanentInfoSig)
1424
1425 print("preparing B")
1426 let (bPeerID, bPermanentInfo, bPermanentInfoSig, _, _, error2) =
1427 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1428 do {
1429 let state = containerB.getStateSync(test: self)
1430 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1431 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1432 XCTAssertNotNil(secret, "secret should not be nil")
1433 XCTAssertNil(error, "error should be nil")
1434 }
1435 XCTAssertNil(error2)
1436 XCTAssertNotNil(bPeerID)
1437 XCTAssertNotNil(bPermanentInfo)
1438 XCTAssertNotNil(bPermanentInfoSig)
1439
1440 // Now, A establishes preapproving B
1441 // Note: secd is responsible for passing in TLKShares to these preapproved keys in sosTLKShares
1442
1443 let bPermanentInfoParsed = TPPeerPermanentInfo(peerID: bPeerID!, data: bPermanentInfo!, sig: bPermanentInfoSig!, keyFactory: TPECPublicKeyFactory())
1444 XCTAssertNotNil(bPermanentInfoParsed, "Should have parsed B's permanent info")
1445
1446 print("establishing A")
1447 do {
1448 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [bPermanentInfoParsed!.signingPubKey.spki()])
1449 XCTAssertNil(error)
1450 XCTAssertNotNil(peerID)
1451 }
1452
1453 do {
1454 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1455
1456 print("B joins by preapproval, and uploads all TLKShares that it has")
1457 let (bJoinedPeerID, _, bJoinedError) = containerB.preapprovedJoinSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [])
1458 XCTAssertNil(bJoinedError, "Should be no error joining by preapproval")
1459 XCTAssertNotNil(bJoinedPeerID, "Should have a peer ID out of join")
1460
1461 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1462 }
1463
1464 _ = containerA.dumpSync(test: self)
1465 _ = containerB.dumpSync(test: self)
1466 }
1467
1468 func testDepart() throws {
1469 let description = tmpStoreDescription(name: "container.db")
1470 let (container, peerID) = try establish(reload: false, store: description)
1471
1472 XCTAssertNil(container.departByDistrustingSelfSync(test: self), "Should be no error distrusting self")
1473 assertDistrusts(context: container, peerIDs: [peerID])
1474 }
1475
1476 func testDistrustPeers() throws {
1477 let store = tmpStoreDescription(name: "container.db")
1478 let (c, peerID1) = try establish(reload: false, store: store)
1479
1480 let (c2, peerID2) = try joinByVoucher(sponsor: c,
1481 containerID: "second",
1482 machineID: "bbb",
1483 machineIDs: ["aaa", "bbb", "ccc"],
1484 store: store)
1485
1486 let (c3, peerID3) = try joinByVoucher(sponsor: c,
1487 containerID: "third",
1488 machineID: "ccc",
1489 machineIDs: ["aaa", "bbb", "ccc"],
1490 store: store)
1491
1492 let (_, cUpdateError) = c.updateSync(test: self)
1493 XCTAssertNil(cUpdateError, "Should be able to update first container")
1494 assertTrusts(context: c, peerIDs: [peerID1, peerID2, peerID3])
1495
1496 // You can't distrust yourself via peerID.
1497 XCTAssertNotNil(c.distrustSync(test: self, peerIDs: Set([peerID1, peerID2, peerID3])), "Should error trying to distrust yourself via peer ID")
1498 assertTrusts(context: c, peerIDs: [peerID1, peerID2, peerID3])
1499
1500 // Passing in nonsense should error too
1501 XCTAssertNotNil(c.distrustSync(test: self, peerIDs: Set(["not a real peer ID"])), "Should error when passing in unknown peer IDs")
1502
1503 // Now, distrust both peers.
1504 XCTAssertNil(c.distrustSync(test: self, peerIDs: Set([peerID2, peerID3])), "Should be no error distrusting peers")
1505 assertDistrusts(context: c, peerIDs: [peerID2, peerID3])
1506
1507 // peers should accept their fates
1508 let (_, c2UpdateError) = c2.updateSync(test: self)
1509 XCTAssertNil(c2UpdateError, "Should be able to update second container")
1510 assertDistrusts(context: c2, peerIDs: [peerID2])
1511
1512 let (_, c3UpdateError) = c3.updateSync(test: self)
1513 XCTAssertNil(c3UpdateError, "Should be able to update third container")
1514 assertDistrusts(context: c3, peerIDs: [peerID3])
1515 }
1516
1517 func testFetchWithBadChangeToken() throws {
1518 let (c, peerID1) = try establish(reload: false, store: tmpStoreDescription(name: "container.db"))
1519
1520 // But all that goes away, and a new peer establishes
1521 self.cuttlefish.state = FakeCuttlefishServer.State()
1522 let (_, peerID2) = try establish(reload: false, contextID: "second", store: tmpStoreDescription(name: "container-peer2.db"))
1523
1524 // And the first container fetches again, which should succeed
1525 self.cuttlefish.nextFetchErrors.append(FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .changeTokenExpired))
1526 let (_, updateError) = c.updateSync(test: self)
1527 XCTAssertNil(updateError, "Update should have succeeded")
1528
1529 // and c's model should only include peerID2
1530 c.moc.performAndWait {
1531 let modelPeers = c.model.allPeerIDs()
1532 XCTAssertEqual(modelPeers.count, 1, "Model should have one peer")
1533 XCTAssert(modelPeers.contains(peerID2), "Model should contain peer 2")
1534 XCTAssertFalse(modelPeers.contains(peerID1), "Model should no longer container peer 1 (ego peer)")
1535 }
1536 }
1537
1538 func testFetchEscrowContents() throws {
1539 let description = tmpStoreDescription(name: "container.db")
1540 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1541 let (entropyA, bottleIDA, spkiA, errorA) = containerA.fetchEscrowContentsSync(test: self)
1542 XCTAssertNotNil(errorA, "Should be an error fetching escrow contents")
1543 XCTAssertEqual(errorA.debugDescription, "Optional(TrustedPeersHelperUnitTests.ContainerError.noPreparedIdentity)", "error should be no prepared identity")
1544 XCTAssertNil(entropyA, "Should not have some entropy to bottle")
1545 XCTAssertNil(bottleIDA, "Should not have a bottleID")
1546 XCTAssertNil(spkiA, "Should not have an SPKI")
1547
1548 let (c, peerID) = try establish(reload: false, store: description)
1549 XCTAssertNotNil(peerID, "establish should return a peer id")
1550
1551 let (entropy, bottleID, spki, error) = c.fetchEscrowContentsSync(test: self)
1552 XCTAssertNil(error, "Should be no error fetching escrow contents")
1553 XCTAssertNotNil(entropy, "Should have some entropy to bottle")
1554 XCTAssertNotNil(bottleID, "Should have a bottleID")
1555 XCTAssertNotNil(spki, "Should have an SPKI")
1556 }
1557
1558 func testBottles() {
1559 do {
1560 let peerSigningKey = _SFECKeyPair.init(randomKeyPairWith: _SFECKeySpecifier.init(curve: SFEllipticCurve.nistp384))!
1561 let peerEncryptionKey = _SFECKeyPair.init(randomKeyPairWith: _SFECKeySpecifier.init(curve: SFEllipticCurve.nistp384))!
1562 let bottle = try BottledPeer(peerID: "peerID", bottleID: UUID().uuidString, peerSigningKey: peerSigningKey, peerEncryptionKey: peerEncryptionKey, bottleSalt: "123456789")
1563
1564 let keys = bottle.escrowKeys
1565 XCTAssertNotNil(keys, "keys should not be nil")
1566
1567 XCTAssertNotNil(bottle, "bottle should not be nil")
1568
1569 XCTAssertNotNil(bottle.escrowSigningPublicKeyHash(), "escrowSigningPublicKeyHash should not be nil")
1570
1571 XCTAssertThrowsError(try BottledPeer.verifyBottleSignature(data: bottle.contents, signature: bottle.signatureUsingPeerKey, pubKey: keys.signingKey.publicKey() as! _SFECPublicKey))
1572 XCTAssertThrowsError(try BottledPeer.verifyBottleSignature(data: bottle.contents, signature: bottle.signatureUsingEscrowKey, pubKey: peerSigningKey.publicKey() as! _SFECPublicKey))
1573
1574 XCTAssertNotNil(BottledPeer.signingOperation(), "signing operation should not be nil")
1575
1576 let verifyBottleEscrowSignature = try BottledPeer.verifyBottleSignature(data: bottle.contents, signature: bottle.signatureUsingEscrowKey, pubKey: keys.signingKey.publicKey() as! _SFECPublicKey)
1577 XCTAssertNotNil(verifyBottleEscrowSignature, "verifyBottleEscrowSignature should not be nil")
1578
1579 let verifyBottlePeerSignature = try BottledPeer.verifyBottleSignature(data: bottle.contents, signature: bottle.signatureUsingPeerKey, pubKey: peerSigningKey.publicKey() as! _SFECPublicKey)
1580 XCTAssertNotNil(verifyBottlePeerSignature, "verifyBottlePeerSignature should not be nil")
1581
1582 let deserializedBottle = try BottledPeer(contents: bottle.contents, secret: bottle.secret, bottleSalt: "123456789", signatureUsingEscrow: bottle.signatureUsingEscrowKey, signatureUsingPeerKey: bottle.signatureUsingPeerKey)
1583 XCTAssertNotNil(deserializedBottle, "deserializedBottle should not be nil")
1584
1585 XCTAssertEqual(deserializedBottle.contents, bottle.contents, "bottle data should be equal")
1586
1587 } catch {
1588 XCTFail("error testing bottles \(error)")
1589 }
1590 }
1591
1592 func testFetchBottles() throws {
1593 let store = tmpStoreDescription(name: "container.db")
1594 let (c, _) = try establish(reload: false, store: store)
1595
1596 let (bottles, _, fetchError) = c.fetchViableBottlesSync(test: self)
1597 XCTAssertNil(fetchError, "should be no error fetching viable bottles")
1598 XCTAssertNotNil(bottles, "should have fetched some bottles")
1599 XCTAssertEqual(bottles!.count, 1, "should have fetched one bottle")
1600
1601 do {
1602 let state = c.getStateSync(test: self)
1603 XCTAssertEqual(state.bottles.count, 1, "first container should have a bottle for peer")
1604 }
1605
1606 let c2 = try Container(name: ContainerName(container: "test", context: "newcomer"), persistentStoreDescription: store, cuttlefish: self.cuttlefish)
1607 do {
1608 let state = c2.getStateSync(test: self)
1609 XCTAssertEqual(state.bottles.count, 0, "before fetch, second container should not have any stored bottles")
1610 }
1611
1612 let (c2bottles, _, c2FetchError) = c2.fetchViableBottlesSync(test: self)
1613 XCTAssertNil(c2FetchError, "should be no error fetching viable bottles")
1614 XCTAssertNotNil(c2bottles, "should have fetched some bottles")
1615 XCTAssertEqual(c2bottles!.count, 1, "should have fetched one bottle")
1616
1617 do {
1618 let state = c2.getStateSync(test: self)
1619 XCTAssertEqual(state.bottles.count, 1, "after fetch, second container should have one stored bottles")
1620 }
1621 }
1622
1623 func testTrustStatus() throws {
1624 let store = tmpStoreDescription(name: "container.db")
1625
1626 let preC = try Container(name: ContainerName(container: "preC", context: "preCContext"),
1627 persistentStoreDescription: store,
1628 cuttlefish: self.cuttlefish)
1629 let (preEgoStatus, precStatusError) = preC.trustStatusSync(test: self)
1630 XCTAssertNil(precStatusError, "No error fetching status")
1631 XCTAssertEqual(preEgoStatus.egoStatus, .unknown, "Before establish, trust status should be 'unknown'")
1632 XCTAssertNil(preEgoStatus.egoPeerID, "should not have a peer ID")
1633 XCTAssertEqual(preEgoStatus.numberOfPeersInOctagon, 0, "should not see number of peers")
1634 XCTAssertFalse(preEgoStatus.isExcluded, "should be excluded")
1635
1636 let (c, _) = try establish(reload: false, store: store)
1637 let (cEgoStatus, cStatusError) = c.trustStatusSync(test: self)
1638 XCTAssertNil(cStatusError, "Should be no error fetching trust status directly after establish")
1639 XCTAssertEqual(cEgoStatus.egoStatus, [.fullyReciprocated, .selfTrust], "After establish, should be fully reciprocated")
1640 XCTAssertNotNil(cEgoStatus.egoPeerID, "should have a peer ID")
1641 XCTAssertEqual(cEgoStatus.numberOfPeersInOctagon, 1, "should be 1 peer")
1642 XCTAssertFalse(cEgoStatus.isExcluded, "should not be excluded")
1643
1644 let c2 = try Container(name: ContainerName(container: "differentContainer", context: "a different context"),
1645 persistentStoreDescription: store,
1646 cuttlefish: self.cuttlefish)
1647
1648 let (egoStatus, statusError) = c2.trustStatusSync(test: self)
1649 XCTAssertNil(statusError, "No error fetching status")
1650 XCTAssertEqual(egoStatus.egoStatus, .excluded, "After establish, other container should be 'excluded'")
1651 XCTAssertNil(egoStatus.egoPeerID, "should not have a peerID")
1652 XCTAssertEqual(egoStatus.numberOfPeersInOctagon, 1, "should be 1 peer")
1653 XCTAssertTrue(egoStatus.isExcluded, "should not be excluded")
1654 }
1655
1656 func testTrustStatusWhenMissingIdentityKeys() throws {
1657 let store = tmpStoreDescription(name: "container.db")
1658 let (c, _) = try establish(reload: false, store: store)
1659 let (cEgoStatus, cStatusError) = c.trustStatusSync(test: self)
1660 XCTAssertNil(cStatusError, "Should be no error fetching trust status directly after establish")
1661 XCTAssertEqual(cEgoStatus.egoStatus, [.fullyReciprocated, .selfTrust], "After establish, should be fully reciprocated")
1662 XCTAssertNotNil(cEgoStatus.egoPeerID, "should have a peer ID")
1663 XCTAssertEqual(cEgoStatus.numberOfPeersInOctagon, 1, "should be 1 peer")
1664 XCTAssertFalse(cEgoStatus.isExcluded, "should not be excluded")
1665
1666 let result = try removeEgoKeysSync(peerID: cEgoStatus.egoPeerID!)
1667 XCTAssertTrue(result, "result should be true")
1668
1669 let (distrustedStatus, distrustedError) = c.trustStatusSync(test: self)
1670 XCTAssertNotNil(distrustedError, "error should not be nil")
1671 XCTAssertEqual(distrustedStatus.egoStatus, [.excluded], "trust status should be excluded")
1672 XCTAssertTrue(distrustedStatus.isExcluded, "should be excluded")
1673 }
1674
1675 func testSetRecoveryKey() throws {
1676 let store = tmpStoreDescription(name: "container.db")
1677
1678 let c = try Container(name: ContainerName(container: "c", context: "context"),
1679 persistentStoreDescription: store,
1680 cuttlefish: self.cuttlefish)
1681
1682 let machineIDs = Set(["aaa", "bbb", "ccc"])
1683 XCTAssertNil(c.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1684
1685 print("preparing peer A")
1686 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1687 c.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1688 do {
1689 let state = c.getStateSync(test: self)
1690 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1691 let secret = c.loadSecretSync(test: self, label: aPeerID!)
1692 XCTAssertNotNil(secret, "secret should not be nil")
1693 XCTAssertNil(error, "error should be nil")
1694 }
1695 XCTAssertNil(error)
1696 XCTAssertNotNil(aPeerID)
1697 XCTAssertNotNil(aPermanentInfo)
1698 XCTAssertNotNil(aPermanentInfoSig)
1699
1700 print("establishing A")
1701 do {
1702 let (peerID, _, error) = c.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: nil)
1703 XCTAssertNil(error)
1704 XCTAssertNotNil(peerID)
1705 }
1706 let recoveryKey = SecRKCreateRecoveryKeyString(nil)
1707 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
1708
1709 let (setRecoveryError) = c.setRecoveryKeySync(test: self, recoveryKey: recoveryKey!, recoverySalt: "altDSID", ckksKeys: [])
1710 XCTAssertNil(setRecoveryError, "error should be nil")
1711 }
1712
1713 func roundTripThroughSetValueTransformer(set: Set<String>) {
1714 let t = SetValueTransformer()
1715
1716 let transformedSet = t.transformedValue(set) as? Data
1717 XCTAssertNotNil(transformedSet, "SVT should return some data")
1718
1719 let recoveredSet = t.reverseTransformedValue(transformedSet) as? Set<String>
1720 XCTAssertNotNil(recoveredSet, "SVT should return some recovered set")
1721
1722 XCTAssertEqual(set, recoveredSet, "Recovered set should be the same as original")
1723 }
1724
1725 func testSetValueTransformer() {
1726 roundTripThroughSetValueTransformer(set: Set<String>([]))
1727 roundTripThroughSetValueTransformer(set: Set<String>(["asdf"]))
1728 roundTripThroughSetValueTransformer(set: Set<String>(["asdf", "three", "test"]))
1729 }
1730
1731 func testGetRepairSuggestion() throws {
1732 let store = tmpStoreDescription(name: "container.db")
1733
1734 let c = try Container(name: ContainerName(container: "c", context: "context"),
1735 persistentStoreDescription: store,
1736 cuttlefish: self.cuttlefish)
1737
1738 let machineIDs = Set(["aaa", "bbb", "ccc"])
1739 XCTAssertNil(c.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1740
1741 print("preparing peer A")
1742 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1743 c.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1744 do {
1745 let state = c.getStateSync(test: self)
1746 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1747 let secret = c.loadSecretSync(test: self, label: aPeerID!)
1748 XCTAssertNotNil(secret, "secret should not be nil")
1749 XCTAssertNil(error, "error should be nil")
1750 }
1751 XCTAssertNil(error)
1752 XCTAssertNotNil(aPeerID)
1753 XCTAssertNotNil(aPermanentInfo)
1754 XCTAssertNotNil(aPermanentInfoSig)
1755
1756 print("establishing A")
1757 do {
1758 let (peerID, _, error) = c.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: nil)
1759 XCTAssertNil(error)
1760 XCTAssertNotNil(peerID)
1761 }
1762 let (repairAccount, repairEscrow, resetOctagon, healthError) = c.requestHealthCheckSync(requiresEscrowCheck: true, test: self)
1763 XCTAssertEqual(repairAccount, false, "")
1764 XCTAssertEqual(repairEscrow, false, "")
1765 XCTAssertEqual(resetOctagon, false, "")
1766 XCTAssertNil(healthError)
1767 }
1768
1769 func testFetchChangesFailDuringVouchWithBottle() throws {
1770 var bottleA: BottleMO
1771 var entropy: Data
1772 let description = tmpStoreDescription(name: "container.db")
1773 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1774 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1775
1776 let machineIDs = Set(["aaa", "bbb"])
1777 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1778 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1779
1780 print("preparing A")
1781 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1782 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1783 do {
1784 var state = containerA.getStateSync(test: self)
1785 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1786
1787 bottleA = state.bottles.removeFirst()
1788
1789 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1790 XCTAssertNotNil(secret, "secret should not be nil")
1791 XCTAssertNil(error, "error should be nil")
1792 }
1793 XCTAssertNotNil(aPeerID)
1794 XCTAssertNotNil(aPermanentInfo)
1795 XCTAssertNotNil(aPermanentInfoSig)
1796
1797 print("establishing A")
1798 do {
1799 assertNoTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1800 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
1801 XCTAssertNil(error)
1802 XCTAssertNotNil(peerID)
1803 assertTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1804 }
1805 do {
1806 let state = containerA.getStateSync(test: self)
1807 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1808 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1809 entropy = secret!
1810 XCTAssertNotNil(secret, "secret should not be nil")
1811 XCTAssertNil(error, "error should be nil")
1812 }
1813
1814 _ = containerB.updateSync(test: self)
1815
1816 print("preparing B")
1817 let (bPeerID, _, _, _, _, error2) =
1818 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1819 do {
1820 let state = containerB.getStateSync(test: self)
1821 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1822 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1823 XCTAssertNotNil(secret, "secret should not be nil")
1824 XCTAssertNil(error, "error should be nil")
1825 }
1826 XCTAssertNil(error2)
1827
1828 do {
1829 print("B prepares to join via bottle")
1830
1831 let (bottlePeerID, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!)
1832 XCTAssertNil(errorPreflight, "Should be no error preflighting a vouch with bottle")
1833 XCTAssertEqual(bottlePeerID, aPeerID, "Bottle should be for peer A")
1834
1835 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1836
1837 XCTAssertNil(error3)
1838 XCTAssertNotNil(voucherData)
1839 XCTAssertNotNil(voucherSig)
1840
1841 self.cuttlefish.nextFetchErrors.append(FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .changeTokenExpired))
1842
1843 // Before B joins, there should be no TLKShares for B
1844 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1845
1846 print("B joins")
1847 let (peerID, _, error) = containerB.joinSync(test: self, voucherData: voucherData!, voucherSig: voucherSig!, ckksKeys: [self.manateeKeySet], tlkShares: [])
1848 XCTAssertNotNil(error)
1849 XCTAssertNil(peerID)
1850 }
1851 }
1852
1853 func testDistrustedPeerRecoveryKeyNotSet() throws {
1854 let description = tmpStoreDescription(name: "container.db")
1855 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1856 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1857
1858 let machineIDs = Set(["aaa", "bbb"])
1859 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1860 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1861
1862 print("preparing peer A")
1863 let (aPeerID, aPermanentInfo, aPermanentInfoSig, aStableInfo, aStableInfoSig, error) =
1864 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1865 do {
1866 let state = containerA.getStateSync(test: self)
1867 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1868 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1869 XCTAssertNotNil(secret, "secret should not be nil")
1870 XCTAssertNil(error, "error should be nil")
1871 }
1872 XCTAssertNil(error)
1873 XCTAssertNotNil(aPeerID)
1874 XCTAssertNotNil(aPermanentInfo)
1875 XCTAssertNotNil(aPermanentInfoSig)
1876 XCTAssertNotNil(aStableInfo)
1877 XCTAssertNotNil(aStableInfoSig)
1878
1879 print("establishing A")
1880 do {
1881 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: nil)
1882 XCTAssertNil(error)
1883 XCTAssertNotNil(peerID)
1884 }
1885
1886 print("preparing B")
1887 let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, error2) =
1888 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1889 do {
1890 let state = containerB.getStateSync(test: self)
1891 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1892 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1893 XCTAssertNotNil(secret, "secret should not be nil")
1894 XCTAssertNil(error, "error should be nil")
1895 }
1896 XCTAssertNil(error2)
1897 XCTAssertNotNil(bPeerID)
1898 XCTAssertNotNil(bPermanentInfo)
1899 XCTAssertNotNil(bPermanentInfoSig)
1900
1901 do {
1902 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1903 print("A vouches for B, but doesn't provide any TLKShares")
1904 let (_, _, errorVouchingWithoutTLKs) =
1905 containerA.vouchSync(test: self,
1906 peerID: bPeerID!,
1907 permanentInfo: bPermanentInfo!,
1908 permanentInfoSig: bPermanentInfoSig!,
1909 stableInfo: bStableInfo!,
1910 stableInfoSig: bStableInfoSig!,
1911 ckksKeys: [])
1912 XCTAssertNil(errorVouchingWithoutTLKs, "Should be no error vouching without uploading TLKShares")
1913 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1914
1915 print("A vouches for B, but doesn't only has provisional TLKs at the time")
1916 let provisionalManateeKeySet = try self.makeFakeKeyHierarchy(zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1917 provisionalManateeKeySet.newUpload = true
1918
1919 let (_, _, errorVouchingWithProvisionalTLKs) =
1920 containerA.vouchSync(test: self,
1921 peerID: bPeerID!,
1922 permanentInfo: bPermanentInfo!,
1923 permanentInfoSig: bPermanentInfoSig!,
1924 stableInfo: bStableInfo!,
1925 stableInfoSig: bStableInfoSig!,
1926 ckksKeys: [provisionalManateeKeySet])
1927 XCTAssertNil(errorVouchingWithProvisionalTLKs, "Should be no error vouching without uploading TLKShares for a non-existent key")
1928 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1929
1930 print("A vouches for B")
1931 let (voucherData, voucherSig, error3) =
1932 containerA.vouchSync(test: self,
1933 peerID: bPeerID!,
1934 permanentInfo: bPermanentInfo!,
1935 permanentInfoSig: bPermanentInfoSig!,
1936 stableInfo: bStableInfo!,
1937 stableInfoSig: bStableInfoSig!,
1938 ckksKeys: [self.manateeKeySet])
1939 XCTAssertNil(error3)
1940 XCTAssertNotNil(voucherData)
1941 XCTAssertNotNil(voucherSig)
1942
1943 // As part of the vouch, A should have uploaded a tlkshare for B
1944 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1945
1946 print("B joins")
1947 let (peerID, _, error) = containerB.joinSync(test: self,
1948 voucherData: voucherData!,
1949 voucherSig: voucherSig!,
1950 ckksKeys: [],
1951 tlkShares: [])
1952 XCTAssertNil(error)
1953 XCTAssertEqual(peerID, bPeerID!)
1954 }
1955
1956 print("A updates")
1957 do {
1958 let (_, error) = containerA.updateSync(test: self)
1959 XCTAssertNil(error)
1960 }
1961 print("B updates")
1962 do {
1963 let (_, error) = containerB.updateSync(test: self)
1964 XCTAssertNil(error)
1965 }
1966
1967 // Now, A distrusts B.
1968 XCTAssertNil(containerA.distrustSync(test: self, peerIDs: Set([bPeerID!])), "Should be no error distrusting peers")
1969 assertDistrusts(context: containerA, peerIDs: [bPeerID!])
1970
1971 let recoveryKey = SecRKCreateRecoveryKeyString(nil)
1972 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
1973
1974 let (setRecoveryError) = containerB.setRecoveryKeySync(test: self, recoveryKey: recoveryKey!, recoverySalt: "altDSID", ckksKeys: [])
1975 XCTAssertNil(setRecoveryError, "error should be nil")
1976
1977 print("A updates")
1978 do {
1979 let (_, error) = containerA.updateSync(test: self)
1980 XCTAssertNil(error)
1981 }
1982 print("B updates")
1983 do {
1984 let (_, error) = containerB.updateSync(test: self)
1985 XCTAssertNil(error)
1986 }
1987
1988 do {
1989 let (dict, error) = containerA.dumpSync(test: self)
1990
1991 XCTAssertNil(error, "Should be no error dumping")
1992 XCTAssertNotNil(dict, "Should receive a dump dictionary")
1993
1994 let selfInfo: [AnyHashable: Any]? = dict!["self"] as! [AnyHashable: Any]?
1995 XCTAssertNotNil(selfInfo, "Should have a self dictionary")
1996
1997 let stableInfo: [AnyHashable: Any]? = selfInfo!["stableInfo"] as! [AnyHashable: Any]?
1998 XCTAssertNotNil(stableInfo, "stableInfo should not be nil")
1999
2000 let recoverySigningPublicKey: Data? = stableInfo!["recovery_signing_public_key"] as! Data?
2001 XCTAssertNil(recoverySigningPublicKey, "recoverySigningPublicKey should be nil")
2002
2003 let recoveryEncryptionPublicKey: Data? = stableInfo!["recovery_encryption_public_key"] as! Data?
2004 XCTAssertNil(recoveryEncryptionPublicKey, "recoveryEncryptionPublicKey should be nil")
2005
2006 }
2007 }
2008
2009 func assert(container: Container,
2010 allowedMachineIDs: Set<String>,
2011 disallowedMachineIDs: Set<String>,
2012 unknownMachineIDs: Set<String> = Set(),
2013 persistentStore: NSPersistentStoreDescription,
2014 cuttlefish: FakeCuttlefishServer) throws {
2015
2016 let midList = container.onqueueCurrentMIDList()
2017 XCTAssertEqual(midList.machineIDs(in: .allowed), allowedMachineIDs, "List of allowed machine IDs should match")
2018 XCTAssertEqual(midList.machineIDs(in: .disallowed), disallowedMachineIDs, "List of disallowed machine IDs should match")
2019 XCTAssertEqual(midList.machineIDs(in: .unknown), unknownMachineIDs, "List of unknown machine IDs should match")
2020
2021
2022 let (fetchedAllowList, fetchErr) = container.fetchAllowedMachineIDsSync(test: self)
2023 XCTAssertNil(fetchErr, "Should be no error fetching the allowed list")
2024 XCTAssertEqual(fetchedAllowList, allowedMachineIDs, "A fetched list of allowed machine IDs should match the loaded list")
2025
2026 // if we reload the container, does it still match?
2027 let reloadedContainer = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: persistentStore, cuttlefish: cuttlefish)
2028
2029 let reloadedMidList = reloadedContainer.onqueueCurrentMIDList()
2030 XCTAssertEqual(reloadedMidList.machineIDs(in: .allowed), allowedMachineIDs, "List of allowed machine IDs on a reloaded container should match")
2031 XCTAssertEqual(reloadedMidList.machineIDs(in: .disallowed), disallowedMachineIDs, "List of disallowed machine IDs on a reloaded container should match")
2032 XCTAssertEqual(reloadedMidList.machineIDs(in: .unknown), unknownMachineIDs, "List of unknown machine IDs on a reloaded container should match")
2033 }
2034
2035 func testAllowListManipulation() throws {
2036 let description = tmpStoreDescription(name: "container.db")
2037 let container = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
2038
2039 let (peerID, permanentInfo, permanentInfoSig, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
2040
2041 XCTAssertNil(error)
2042 XCTAssertNotNil(peerID)
2043 XCTAssertNotNil(permanentInfo)
2044 XCTAssertNotNil(permanentInfoSig)
2045
2046 try self.assert(container: container, allowedMachineIDs: [], disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2047
2048 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"]), "should be able to set allowed machine IDs")
2049 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2050 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2051
2052 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"]), "should be able to set allowed machine IDs")
2053 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb"]), disallowedMachineIDs: Set(["ccc"]), persistentStore: description, cuttlefish: self.cuttlefish)
2054 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2055
2056 XCTAssertNil(container.addAllowedMachineIDsSync(test: self, machineIDs: ["zzz", "kkk"]), "should be able to add allowed machine IDs")
2057 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "zzz", "kkk"]), disallowedMachineIDs: Set(["ccc"]), persistentStore: description, cuttlefish: self.cuttlefish)
2058 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2059
2060 // Receivng a 'remove' push should send the MIDs to the 'unknown' list
2061 XCTAssertNil(container.removeAllowedMachineIDsSync(test: self, machineIDs: ["bbb", "fff"]), "should be able to remove allowed machine IDs")
2062 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "zzz", "kkk"]), disallowedMachineIDs: Set(["ccc"]), unknownMachineIDs: Set(["bbb", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2063 XCTAssertTrue(container.onqueueFullIDMSListWouldBeHelpful(), "Container should think it could use an IDMS list set: there's machine IDs pending removal")
2064
2065 // once they're unknown, a full list set will make them disallowed
2066 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "zzz", "kkk"]), "should be able to set allowed machine IDs")
2067 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "zzz", "kkk"]), disallowedMachineIDs: Set(["bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2068 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2069
2070 // Resetting the list to what it is doesn't change the list
2071 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "zzz", "kkk"], listDifference: false), "should be able to set allowed machine IDs")
2072 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "zzz", "kkk"]), disallowedMachineIDs: Set(["bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2073 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2074
2075 // But changing it to something completely new does
2076 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["xxx", "mmm"]), "should be able to set allowed machine IDs")
2077 try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm"]), disallowedMachineIDs: Set(["aaa", "zzz", "kkk", "bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2078 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2079
2080 // And, readding a previously disallowed machine ID works too
2081 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["xxx", "mmm", "aaa"]), "should be able to set allowed machine IDs")
2082 try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm", "aaa"]), disallowedMachineIDs: Set(["zzz", "kkk", "bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2083 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2084
2085 // A update() before establish() doesn't change the list, since it isn't actually changing anything
2086 let (_, updateError) = container.updateSync(test: self)
2087 XCTAssertNil(updateError, "Should not be an error updating the container without first establishing")
2088 try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm", "aaa"]), disallowedMachineIDs: Set(["zzz", "kkk", "bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2089
2090 let (_, _, establishError) = container.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
2091 XCTAssertNil(establishError, "Should be able to establish() with no error")
2092 try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm", "aaa"]), disallowedMachineIDs: Set(["zzz", "kkk", "bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2093 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2094
2095 // But a successful update() does remove all disallowed machine IDs, as they're no longer relevant
2096 let (_, updateError2) = container.updateSync(test: self)
2097 XCTAssertNil(updateError2, "Should not be an error updating the container after establishing")
2098 try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm", "aaa"]), disallowedMachineIDs: Set([]), persistentStore: description, cuttlefish: self.cuttlefish)
2099 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2100 }
2101
2102 func testAllowListManipulationWithAddsAndRemoves() throws {
2103 let description = tmpStoreDescription(name: "container.db")
2104 let container = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
2105
2106 try self.assert(container: container, allowedMachineIDs: [], disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2107
2108 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"], listDifference: true), "should be able to set allowed machine IDs")
2109 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2110 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2111
2112 // Now, an 'add' comes in for some peers
2113 XCTAssertNil(container.addAllowedMachineIDsSync(test: self, machineIDs: ["ddd", "eee"]), "should be able to receive an add push")
2114 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc", "ddd", "eee"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2115 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2116
2117 // But, the next time we ask IDMS, they still haven't made it to the full list, and in fact, C has disappeared.
2118 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], listDifference: true), "should be able to set allowed machine IDs")
2119 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd", "eee"]), disallowedMachineIDs: Set(["ccc"]), persistentStore: description, cuttlefish: self.cuttlefish)
2120 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2121
2122 // And a remove comes in for E. It becomes 'unknown'
2123 XCTAssertNil(container.removeAllowedMachineIDsSync(test: self, machineIDs: ["eee"]), "should be able to receive an add push")
2124 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd"]), disallowedMachineIDs: Set(["ccc"]), unknownMachineIDs: Set(["eee"]), persistentStore: description, cuttlefish: self.cuttlefish)
2125 XCTAssertTrue(container.onqueueFullIDMSListWouldBeHelpful(), "Container should think it could use an IDMS list set: there's machine IDs pending removal")
2126
2127 // and a list set after the remove confirms the removal
2128 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], listDifference: true), "should be able to set allowed machine IDs")
2129 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd"]), disallowedMachineIDs: Set(["ccc", "eee"]), persistentStore: description, cuttlefish: self.cuttlefish)
2130 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2131
2132 // Then a new list set includes D! Hurray IDMS. Note that this is not a "list change", because the list doesn't actually change
2133 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ddd"], listDifference: false), "should be able to set allowed machine IDs")
2134 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd"]), disallowedMachineIDs: Set(["ccc", "eee"]), persistentStore: description, cuttlefish: self.cuttlefish)
2135 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2136
2137 // And another list set no longer includes D, so it should now be disallowed
2138 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], listDifference: true), "should be able to set allowed machine IDs")
2139 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb"]), disallowedMachineIDs: Set(["ccc", "ddd", "eee"]), persistentStore: description, cuttlefish: self.cuttlefish)
2140 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2141
2142 // And just to check the 48 hour boundary...
2143 XCTAssertNil(container.addAllowedMachineIDsSync(test: self, machineIDs: ["xxx"]), "should be able to receive an add push")
2144 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], listDifference: false), "should be able to set allowed machine IDs")
2145 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "xxx"]), disallowedMachineIDs: Set(["ccc", "ddd", "eee"]), persistentStore: description, cuttlefish: self.cuttlefish)
2146
2147 container.moc.performAndWait {
2148 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2149
2150 knownMachineMOs.forEach {
2151 if $0.machineID == "xxx" {
2152 $0.modified = Date(timeIntervalSinceNow: -60 * 60 * 72)
2153 }
2154 }
2155
2156 try! container.moc.save()
2157 }
2158
2159 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2160
2161 // Setting the list again should kick out X, since it was 'added' too long ago
2162 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], listDifference: true), "should be able to set allowed machine IDs")
2163 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb"]), disallowedMachineIDs: Set(["ccc", "ddd", "eee", "xxx"]), persistentStore: description, cuttlefish: self.cuttlefish)
2164 }
2165
2166 func testAllowSetUpgrade() throws {
2167 let containerName = ContainerName(container: "test", context: OTDefaultContext)
2168
2169 let description = tmpStoreDescription(name: "container.db")
2170 let url = Bundle(for: type(of: self)).url(forResource: "TrustedPeersHelper", withExtension: "momd")!
2171 let mom = getOrMakeModel(url: url)
2172 let persistentContainer = NSPersistentContainer(name: "TrustedPeersHelper", managedObjectModel: mom)
2173 persistentContainer.persistentStoreDescriptions = [description]
2174
2175 persistentContainer.loadPersistentStores { _, error in
2176 XCTAssertNil(error, "Should be no error loading persistent stores")
2177 }
2178
2179 let moc = persistentContainer.newBackgroundContext()
2180 moc.performAndWait {
2181 let containerMO = ContainerMO(context: moc)
2182 containerMO.name = containerName.asSingleString()
2183 containerMO.allowedMachineIDs = Set(["aaa", "bbb", "ccc"]) as NSSet
2184
2185 XCTAssertNoThrow(try! moc.save())
2186 }
2187
2188 // Now TPH boots up with a preexisting model
2189 let container = try Container(name: containerName, persistentStoreDescription: description, cuttlefish: cuttlefish)
2190
2191 let (peerID, permanentInfo, permanentInfoSig, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
2192
2193 XCTAssertNil(error)
2194 XCTAssertNotNil(peerID)
2195 XCTAssertNotNil(permanentInfo)
2196 XCTAssertNotNil(permanentInfoSig)
2197
2198 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2199
2200 // Setting a new list should work fine
2201 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ddd"]), "should be able to set allowed machine IDs")
2202 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd"]), disallowedMachineIDs: ["ccc"], persistentStore: description, cuttlefish: self.cuttlefish)
2203
2204 XCTAssertEqual(container.containerMO.allowedMachineIDs, Set<String>() as NSSet, "Set of allowed machine IDs should now be empty")
2205 }
2206
2207 func testAllowStatusUpgrade() throws {
2208 let containerName = ContainerName(container: "test", context: OTDefaultContext)
2209
2210 let description = tmpStoreDescription(name: "container.db")
2211 let url = Bundle(for: type(of: self)).url(forResource: "TrustedPeersHelper", withExtension: "momd")!
2212 let mom = getOrMakeModel(url: url)
2213 let persistentContainer = NSPersistentContainer(name: "TrustedPeersHelper", managedObjectModel: mom)
2214 persistentContainer.persistentStoreDescriptions = [description]
2215
2216 persistentContainer.loadPersistentStores { _, error in
2217 XCTAssertNil(error, "Should be no error loading persistent stores")
2218 }
2219
2220 let moc = persistentContainer.newBackgroundContext()
2221 moc.performAndWait {
2222 let containerMO = ContainerMO(context: moc)
2223 containerMO.name = containerName.asSingleString()
2224
2225 let machine = MachineMO(context: moc)
2226 machine.allowed = true
2227 machine.modified = Date()
2228 machine.machineID = "aaa"
2229 containerMO.addToMachines(machine)
2230
2231 let machineB = MachineMO(context: moc)
2232 machineB.allowed = false
2233 machineB.modified = Date()
2234 machineB.machineID = "bbb"
2235 containerMO.addToMachines(machineB)
2236
2237 try! moc.save()
2238 }
2239
2240 // Now TPH boots up with a preexisting model
2241 let container = try Container(name: containerName, persistentStoreDescription: description, cuttlefish: cuttlefish)
2242 try self.assert(container: container, allowedMachineIDs: Set(["aaa"]), disallowedMachineIDs: ["bbb"], persistentStore: description, cuttlefish: self.cuttlefish)
2243
2244 // Setting a new list should work fine
2245 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "ddd"]), "should be able to set allowed machine IDs")
2246 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "ddd"]), disallowedMachineIDs: ["bbb"], persistentStore: description, cuttlefish: self.cuttlefish)
2247
2248 XCTAssertEqual(container.containerMO.allowedMachineIDs, Set<String>() as NSSet, "Set of allowed machine IDs should now be empty")
2249 }
2250
2251 func testMachineIDListSetWithUnknownMachineIDs() throws {
2252 let description = tmpStoreDescription(name: "container.db")
2253 let (container, _) = try establish(reload: false, store: description)
2254
2255 container.moc.performAndWait {
2256 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2257
2258 knownMachineMOs.forEach {
2259 container.containerMO.removeFromMachines($0)
2260 }
2261
2262 try! container.moc.save()
2263 }
2264
2265 // and set the machine ID list to something that doesn't include 'aaa'
2266 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["bbb", "ccc"], listDifference: true), "should be able to set allowed machine IDs")
2267 try self.assert(container: container, allowedMachineIDs: Set(["bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set(["aaa"]), persistentStore: description, cuttlefish: self.cuttlefish)
2268 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2269
2270 // But check that it exists, and set its modification date to a while ago for an upcoming test
2271 container.moc.performAndWait {
2272 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2273
2274 let aaaMOs = knownMachineMOs.filter { $0.machineID == "aaa" }
2275 XCTAssert(aaaMOs.count == 1, "Should have one machine MO for aaa")
2276
2277 let aaaMO = aaaMOs.first!
2278 XCTAssertEqual(aaaMO.status, Int64(TPMachineIDStatus.unknown.rawValue), "Status of aaa MO should be 'unknown'")
2279 XCTAssertFalse(aaaMO.allowed, "allowed should no longer be a used field")
2280
2281 aaaMO.modified = Date(timeIntervalSinceNow: -60)
2282 try! container.moc.save()
2283 }
2284
2285 // With it 'modified' only 60s ago, we shouldn't want a list fetch
2286 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2287
2288 // Setting it again is fine...
2289 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["bbb", "ccc"], listDifference: false), "should be able to set allowed machine IDs")
2290 try self.assert(container: container, allowedMachineIDs: Set(["bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set(["aaa"]), persistentStore: description, cuttlefish: self.cuttlefish)
2291
2292 // And doesn't reset the modified date on the record
2293 container.moc.performAndWait {
2294 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2295
2296 let aaaMOs = knownMachineMOs.filter { $0.machineID == "aaa" }
2297 XCTAssert(aaaMOs.count == 1, "Should have one machine MO for aaa")
2298
2299 let aaaMO = aaaMOs.first!
2300 XCTAssertEqual(aaaMO.status, Int64(TPMachineIDStatus.unknown.rawValue), "Status of aaa MO should be 'unknown'")
2301 XCTAssertFalse(aaaMO.allowed, "allowed should no longer be a used field")
2302
2303 XCTAssertLessThan(aaaMO.modified!, Date(timeIntervalSinceNow: -5), "Modification date of record should still be its previously on-disk value")
2304 }
2305
2306 // And can be promoted to 'allowed'
2307 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], listDifference: true), "should be able to set allowed machine IDs")
2308 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb"]), disallowedMachineIDs: ["ccc"], persistentStore: description, cuttlefish: self.cuttlefish)
2309 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2310 }
2311
2312 func testMachineIDListSetDisallowedOldUnknownMachineIDs() throws {
2313 let description = tmpStoreDescription(name: "container.db")
2314 let (container, _) = try establish(reload: false, contextID: OTDefaultContext, allowedMachineIDs: Set(), store: description)
2315
2316 // and set the machine ID list to something that doesn't include 'aaa'
2317 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["bbb", "ccc"], listDifference: true), "should be able to set allowed machine IDs")
2318 try self.assert(container: container, allowedMachineIDs: Set(["bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set(["aaa"]), persistentStore: description, cuttlefish: self.cuttlefish)
2319 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2320
2321 // But an entry for "aaa" should exist, as a peer in the model claims it as their MID
2322 container.moc.performAndWait {
2323 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2324
2325 let aaaMOs = knownMachineMOs.filter { $0.machineID == "aaa" }
2326 XCTAssertEqual(aaaMOs.count, 1, "Should have one machine MO for aaa")
2327
2328 let aaaMO = aaaMOs.first!
2329 XCTAssertEqual(aaaMO.status, Int64(TPMachineIDStatus.unknown.rawValue), "Status of aaa MO should be 'unknown'")
2330 XCTAssertFalse(aaaMO.allowed, "allowed should no longer be a used field")
2331
2332 // Pretend that aaa was added 49 hours ago
2333 aaaMO.modified = Date(timeIntervalSinceNow: -60 * 60 * 49)
2334 try! container.moc.save()
2335 }
2336
2337 XCTAssertTrue(container.onqueueFullIDMSListWouldBeHelpful(), "Container should think it could use an IDMS list set: there's machine IDs pending removal")
2338
2339 // And, setting the list again should disallow aaa, since it is so old
2340 // Note that this _should_ return a list difference, since A is promoted to disallowed
2341 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["bbb", "ccc"], listDifference: true), "should be able to set allowed machine IDs")
2342 try self.assert(container: container, allowedMachineIDs: Set(["bbb", "ccc"]), disallowedMachineIDs: ["aaa"], persistentStore: description, cuttlefish: self.cuttlefish)
2343 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2344
2345 // Setting ths list again has no change
2346 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["bbb", "ccc"], listDifference: false), "should be able to set allowed machine IDs")
2347 try self.assert(container: container, allowedMachineIDs: Set(["bbb", "ccc"]), disallowedMachineIDs: ["aaa"], persistentStore: description, cuttlefish: self.cuttlefish)
2348 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2349
2350 // But A can appear again, no problem.
2351 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"], listDifference: true), "should be able to set allowed machine IDs")
2352 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2353 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2354 }
2355
2356 func testMachineIDListHandlingWithPeers() throws {
2357 let description = tmpStoreDescription(name: "container.db")
2358 let (container, peerID1) = try establish(reload: false, store: description)
2359
2360 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set(), persistentStore: description, cuttlefish: self.cuttlefish)
2361
2362 let unknownMachineID = "not-on-list"
2363 let (_, peerID2) = try self.joinByVoucher(sponsor: container,
2364 containerID: "second",
2365 machineID: unknownMachineID,
2366 machineIDs: Set([unknownMachineID, "aaa", "bbb", "ccc"]),
2367 store: description)
2368
2369 // And the first container accepts the join...
2370 let (_, cUpdateError) = container.updateSync(test: self)
2371 XCTAssertNil(cUpdateError, "Should be able to update first container")
2372 assertTrusts(context: container, peerIDs: [peerID1, peerID2])
2373
2374 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set([unknownMachineID]), persistentStore: description, cuttlefish: self.cuttlefish)
2375 }
2376
2377 func testMachineIDListHandlingInDemoAccounts() throws {
2378 // Demo accounts have no machine IDs in their lists
2379 let description = tmpStoreDescription(name: "container.db")
2380 let (container, peerID1) = try establish(reload: false, contextID: OTDefaultContext, allowedMachineIDs: Set(), store: description)
2381
2382 // And so we just don't write down any MIDs
2383 try self.assert(container: container, allowedMachineIDs: Set([]), disallowedMachineIDs: [], unknownMachineIDs: Set([]), persistentStore: description, cuttlefish: self.cuttlefish)
2384
2385 // Even when joining...
2386 let unknownMachineID = "not-on-list"
2387 let (c2, peerID2) = try self.joinByVoucher(sponsor: container,
2388 containerID: "second",
2389 machineID: unknownMachineID,
2390 machineIDs: Set(),
2391 store: description)
2392 try self.assert(container: c2, allowedMachineIDs: Set([]), disallowedMachineIDs: [], unknownMachineIDs: Set([]), persistentStore: description, cuttlefish: self.cuttlefish)
2393
2394 // And the first container accepts the join...
2395 let (_, cUpdateError) = container.updateSync(test: self)
2396 XCTAssertNil(cUpdateError, "Should be able to update first container")
2397 assertTrusts(context: container, peerIDs: [peerID1, peerID2])
2398
2399 // And still has nothing in its list...
2400 try self.assert(container: container, allowedMachineIDs: Set([]), disallowedMachineIDs: [], unknownMachineIDs: Set([]), persistentStore: description, cuttlefish: self.cuttlefish)
2401
2402 // Even after a full list set
2403 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: [], listDifference: false), "should be able to set allowed machine IDs")
2404 try self.assert(container: container, allowedMachineIDs: Set([]), disallowedMachineIDs: [], unknownMachineIDs: Set([]), persistentStore: description, cuttlefish: self.cuttlefish)
2405
2406 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2407 }
2408
2409 func testContainerAndModelConsistency() throws {
2410
2411 let preTestContainerName = ContainerName(container: "testToCreatePrepareData", context: "context")
2412 let description = tmpStoreDescription(name: "container.db")
2413 let containerTest = try Container(name: preTestContainerName, persistentStoreDescription: description, cuttlefish: cuttlefish)
2414 let (peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error) = containerTest.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
2415 XCTAssertNil(error)
2416 XCTAssertNotNil(peerID)
2417 XCTAssertNotNil(permanentInfo)
2418 XCTAssertNotNil(permanentInfoSig)
2419 XCTAssertNotNil(stableInfo)
2420 XCTAssertNotNil(stableInfoSig)
2421
2422 let containerName = ContainerName(container: "test", context: OTDefaultContext)
2423 let url = Bundle(for: type(of: self)).url(forResource: "TrustedPeersHelper", withExtension: "momd")!
2424 let mom = getOrMakeModel(url: url)
2425 let persistentContainer = NSPersistentContainer(name: "TrustedPeersHelper", managedObjectModel: mom)
2426 persistentContainer.persistentStoreDescriptions = [description]
2427
2428 persistentContainer.loadPersistentStores { _, error in
2429 XCTAssertNil(error, "Should be no error loading persistent stores")
2430 }
2431
2432 let moc = persistentContainer.newBackgroundContext()
2433 moc.performAndWait {
2434 let containerMO = ContainerMO(context: moc)
2435 containerMO.name = containerName.asSingleString()
2436 containerMO.allowedMachineIDs = Set(["aaa"]) as NSSet
2437 containerMO.egoPeerID = peerID
2438 containerMO.egoPeerPermanentInfo = permanentInfo
2439 containerMO.egoPeerPermanentInfoSig = permanentInfoSig
2440 containerMO.egoPeerStableInfoSig = stableInfoSig
2441 containerMO.egoPeerStableInfo = stableInfo
2442 let containerEgoStableInfo = TPPeerStableInfo(data: stableInfo!, sig: stableInfoSig!)
2443 do {
2444 let peerKeys: OctagonSelfPeerKeys = try loadEgoKeysSync(peerID: containerMO.egoPeerID!)
2445 let info3 = TPPeerStableInfo(clock: containerEgoStableInfo!.clock + 2,
2446 policyVersion: containerEgoStableInfo!.policyVersion,
2447 policyHash: containerEgoStableInfo!.policyHash,
2448 policySecrets: containerEgoStableInfo!.policySecrets,
2449 deviceName: containerEgoStableInfo!.deviceName,
2450 serialNumber: containerEgoStableInfo!.serialNumber,
2451 osVersion: containerEgoStableInfo!.osVersion,
2452 signing: peerKeys.signingKey,
2453 recoverySigningPubKey: containerEgoStableInfo!.recoverySigningPublicKey,
2454 recoveryEncryptionPubKey: containerEgoStableInfo!.recoveryEncryptionPublicKey,
2455 error: nil)
2456
2457 //setting the containerMO's ego stable info to an old clock
2458 containerMO.egoPeerStableInfo = containerEgoStableInfo!.data
2459 containerMO.egoPeerStableInfoSig = containerEgoStableInfo!.sig
2460
2461 //now we are adding the ego stable info with a clock of 3 to the list of peers
2462 let peer = PeerMO(context: moc)
2463 peer.peerID = peerID
2464 peer.permanentInfo = permanentInfo
2465 peer.permanentInfoSig = permanentInfoSig
2466 peer.stableInfo = info3.data
2467 peer.stableInfoSig = info3.sig
2468 peer.isEgoPeer = true
2469 peer.container = containerMO
2470
2471 containerMO.addToPeers(peer)
2472
2473 //at this point the containerMO's egoStableInfo should have a clock of 1
2474 //the saved ego peer in the peer list has a clock of 3
2475
2476 } catch {
2477 XCTFail("load ego keys failed: \(error)")
2478 }
2479 XCTAssertNoThrow(try! moc.save())
2480 }
2481
2482 // Now TPH boots up with a preexisting model
2483 let container = try Container(name: containerName, persistentStoreDescription: description, cuttlefish: cuttlefish)
2484
2485 let stableInfoAfterBoot = TPPeerStableInfo(data: container.containerMO.egoPeerStableInfo!, sig: container.containerMO.egoPeerStableInfoSig!)
2486 XCTAssertNotNil(stableInfoAfterBoot)
2487
2488 //after boot the clock should be updated to the one that was saved in the model
2489 XCTAssertEqual(stableInfoAfterBoot!.clock, 3, "clock should be updated to 3")
2490 }
2491
2492 func testRetryableError() throws {
2493 XCTAssertTrue(RetryingInvocable.retryableError(error: NSError(domain: NSURLErrorDomain, code: NSURLErrorTimedOut, userInfo: nil)))
2494 XCTAssertFalse(RetryingInvocable.retryableError(error: NSError(domain: NSURLErrorDomain, code: NSURLErrorUnknown, userInfo: nil)))
2495 XCTAssertTrue(RetryingInvocable.retryableError(error: NSError(domain: CKErrorDomain, code: CKError.networkFailure.rawValue, userInfo: nil)))
2496 XCTAssertFalse(RetryingInvocable.retryableError(error: NSError(domain: CKErrorDomain, code: CKError.serverRejectedRequest.rawValue, userInfo: nil)))
2497
2498 let sub0 = NSError(domain: CKInternalErrorDomain, code: CKInternalErrorCode.errorInternalServerInternalError.rawValue, userInfo: nil)
2499 let e0 = NSError(domain: CKErrorDomain, code: CKError.serverRejectedRequest.rawValue, userInfo: [NSUnderlyingErrorKey: sub0])
2500 XCTAssertTrue(RetryingInvocable.retryableError(error: e0))
2501
2502 let sub1 = NSError(domain: CKInternalErrorDomain, code: CKInternalErrorCode.errorInternalGenericError.rawValue, userInfo: nil)
2503 let e1 = NSError(domain: CKErrorDomain, code: CKError.serverRejectedRequest.rawValue, userInfo: [NSUnderlyingErrorKey: sub1])
2504 XCTAssertFalse(RetryingInvocable.retryableError(error: e1))
2505
2506 let cf2 = NSError(domain: CuttlefishErrorDomain, code: CuttlefishErrorCode.changeTokenExpired.rawValue, userInfo: nil)
2507 let int2 = NSError(domain: CKInternalErrorDomain, code: CKInternalErrorCode.errorInternalPluginError.rawValue, userInfo: [NSUnderlyingErrorKey: cf2])
2508 let e2 = NSError(domain: CKErrorDomain, code: CKError.serverRejectedRequest.rawValue, userInfo: [NSUnderlyingErrorKey: int2])
2509 XCTAssertFalse(RetryingInvocable.retryableError(error: e2))
2510
2511 let cf3 = NSError(domain: CuttlefishErrorDomain, code: CuttlefishErrorCode.retryableServerFailure.rawValue, userInfo: nil)
2512 let int3 = NSError(domain: CKInternalErrorDomain, code: CKInternalErrorCode.errorInternalPluginError.rawValue, userInfo: [NSUnderlyingErrorKey: cf3])
2513 let e3 = NSError(domain: CKErrorDomain, code: CKError.serverRejectedRequest.rawValue, userInfo: [NSUnderlyingErrorKey: int3])
2514 XCTAssertTrue(RetryingInvocable.retryableError(error: e3))
2515
2516 let cf4 = NSError(domain: CuttlefishErrorDomain, code: CuttlefishErrorCode.transactionalFailure.rawValue, userInfo: nil)
2517 let int4 = NSError(domain: CKInternalErrorDomain, code: CKInternalErrorCode.errorInternalPluginError.rawValue, userInfo: [NSUnderlyingErrorKey: cf4])
2518 let e4 = NSError(domain: CKErrorDomain, code: CKError.serverRejectedRequest.rawValue, userInfo: [NSUnderlyingErrorKey: int4])
2519 XCTAssertTrue(RetryingInvocable.retryableError(error: e4))
2520 }
2521 }