]> git.saurik.com Git - apple/security.git/blob - keychain/ot/tests/octagon/OctagonTests+EscrowRecovery.swift
d79263f8e3ed491b255b914f5e89ffd72a2091f7
[apple/security.git] / keychain / ot / tests / octagon / OctagonTests+EscrowRecovery.swift
1 #if OCTAGON
2
3 @objcMembers
4 class OctagonEscrowRecoveryTests: OctagonTestsBase {
5 override func setUp() {
6 super.setUp()
7 }
8
9 func testJoinWithBottle() throws {
10 let initiatorContextID = "initiator-context-id"
11 let bottlerContext = self.makeInitiatorContext(contextID: initiatorContextID)
12
13 bottlerContext.startOctagonStateMachine()
14 let ckacctinfo = CKAccountInfo()
15 ckacctinfo.accountStatus = .available
16 ckacctinfo.hasValidCredentials = true
17 ckacctinfo.accountPartition = .production
18
19 bottlerContext.cloudkitAccountStateChange(nil, to: ckacctinfo)
20 XCTAssertNoThrow(try bottlerContext.setCDPEnabled())
21 self.assertEnters(context: bottlerContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
22
23 let clique: OTClique
24 let bottlerotcliqueContext = OTConfigurationContext()
25 bottlerotcliqueContext.context = initiatorContextID
26 bottlerotcliqueContext.dsid = "1234"
27 bottlerotcliqueContext.altDSID = self.mockAuthKit.altDSID!
28 bottlerotcliqueContext.otControl = self.otControl
29 do {
30 clique = try OTClique.newFriends(withContextData: bottlerotcliqueContext, resetReason: .testGenerated)
31 XCTAssertNotNil(clique, "Clique should not be nil")
32 XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call")
33 } catch {
34 XCTFail("Shouldn't have errored making new friends: \(error)")
35 throw error
36 }
37
38 self.assertEnters(context: bottlerContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
39 self.assertConsidersSelfTrusted(context: bottlerContext)
40
41 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
42 XCTAssertNotNil(entropy, "entropy should not be nil")
43
44 // Fake that this peer also created some TLKShares for itself
45 self.putFakeKeyHierarchiesInCloudKit()
46 try self.putSelfTLKSharesInCloudKit(context: bottlerContext)
47
48 let bottle = self.fakeCuttlefishServer.state.bottles[0]
49
50 self.cuttlefishContext.startOctagonStateMachine()
51 self.startCKAccountStatusMock()
52 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
53
54 // Try to enforce that that CKKS doesn't know about the key hierarchy until Octagon asks it
55 self.holdCloudKitFetches()
56
57 // Note: CKKS will want to upload a TLKShare for its self
58 self.assertAllCKKSViewsUpload(tlkShares: 1)
59
60 // Before you call joinWithBottle, you need to call fetchViableBottles.
61 let fetchViableExpectation = self.expectation(description: "fetchViableBottles callback occurs")
62 self.cuttlefishContext.rpcFetchAllViableBottles { viable, _, error in
63 XCTAssertNil(error, "should be no error fetching viable bottles")
64 XCTAssert(viable?.contains(bottle.bottleID) ?? false, "The bottle we're about to restore should be viable")
65 fetchViableExpectation.fulfill()
66 }
67 self.wait(for: [fetchViableExpectation], timeout: 10)
68
69 let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs")
70 self.cuttlefishContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID!) { error in
71 XCTAssertNil(error, "error should be nil")
72 joinWithBottleExpectation.fulfill()
73 }
74
75 sleep(1)
76 self.releaseCloudKitFetchHold()
77
78 self.wait(for: [joinWithBottleExpectation], timeout: 100)
79
80 let dumpCallback = self.expectation(description: "dumpCallback callback occurs")
81 self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) { dump, _ in
82 XCTAssertNotNil(dump, "dump should not be nil")
83 let egoSelf = dump!["self"] as? [String: AnyObject]
84 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
85 let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
86 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
87 let included = dynamicInfo!["included"] as? [String]
88 XCTAssertNotNil(included, "included should not be nil")
89 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
90 dumpCallback.fulfill()
91 }
92 self.wait(for: [dumpCallback], timeout: 10)
93
94 self.verifyDatabaseMocks()
95 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
96 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
97 }
98
99 func testBottleRestoreEntersOctagonReady() throws {
100 self.startCKAccountStatusMock()
101
102 let initiatorContextID = "initiator-context-id"
103 self.cuttlefishContext.startOctagonStateMachine()
104 XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled())
105 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
106
107 let clique: OTClique
108 do {
109 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
110 XCTAssertNotNil(clique, "Clique should not be nil")
111 } catch {
112 XCTFail("Shouldn't have errored making new friends: \(error)")
113 throw error
114 }
115
116 self.verifyDatabaseMocks()
117
118 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
119 XCTAssertNotNil(entropy, "entropy should not be nil")
120
121 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
122 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
123
124 let bottle = self.fakeCuttlefishServer.state.bottles[0]
125
126 let initiatorContext = self.makeInitiatorContext(contextID: initiatorContextID)
127
128 initiatorContext.startOctagonStateMachine()
129 let restoreExpectation = self.expectation(description: "restore returns")
130
131 self.manager!.restore(OTCKContainerName, contextID: initiatorContextID, bottleSalt: self.otcliqueContext.altDSID!, entropy: entropy!, bottleID: bottle.bottleID) { error in
132 XCTAssertNil(error, "error should be nil")
133 restoreExpectation.fulfill()
134 }
135 self.wait(for: [restoreExpectation], timeout: 10)
136
137 self.assertEnters(context: initiatorContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
138
139 let initiatorDumpCallback = self.expectation(description: "initiatorDumpCallback callback occurs")
140 self.tphClient.dump(withContainer: OTCKContainerName, context: initiatorContextID) { dump, _ in
141 XCTAssertNotNil(dump, "dump should not be nil")
142 let egoSelf = dump!["self"] as? [String: AnyObject]
143 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
144 let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
145 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
146 let included = dynamicInfo!["included"] as? [String]
147 XCTAssertNotNil(included, "included should not be nil")
148 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
149
150 initiatorDumpCallback.fulfill()
151 }
152 self.wait(for: [initiatorDumpCallback], timeout: 10)
153 }
154
155 func testJoinWithBottleWithCKKSConflict() throws {
156 let initiatorContextID = "initiator-context-id"
157 let bottlerContext = self.makeInitiatorContext(contextID: initiatorContextID)
158
159 bottlerContext.startOctagonStateMachine()
160 let ckacctinfo = CKAccountInfo()
161 ckacctinfo.accountStatus = .available
162 ckacctinfo.hasValidCredentials = true
163 ckacctinfo.accountPartition = .production
164
165 bottlerContext.cloudkitAccountStateChange(nil, to: ckacctinfo)
166 XCTAssertNoThrow(try bottlerContext.setCDPEnabled())
167 self.assertEnters(context: bottlerContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
168
169 let clique: OTClique
170 let bottlerotcliqueContext = OTConfigurationContext()
171 bottlerotcliqueContext.context = initiatorContextID
172 bottlerotcliqueContext.dsid = "1234"
173 bottlerotcliqueContext.altDSID = self.mockAuthKit.altDSID!
174 bottlerotcliqueContext.otControl = self.otControl
175 do {
176 clique = try OTClique.newFriends(withContextData: bottlerotcliqueContext, resetReason: .testGenerated)
177 XCTAssertNotNil(clique, "Clique should not be nil")
178 XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call")
179 } catch {
180 XCTFail("Shouldn't have errored making new friends: \(error)")
181 throw error
182 }
183
184 self.assertEnters(context: bottlerContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
185 self.assertConsidersSelfTrusted(context: bottlerContext)
186
187 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
188 XCTAssertNotNil(entropy, "entropy should not be nil")
189
190 let bottle = self.fakeCuttlefishServer.state.bottles[0]
191
192 // During the join, there's a CKKS key race
193 self.silentFetchesAllowed = false
194 self.expectCKFetchAndRun {
195 self.putFakeKeyHierarchiesInCloudKit()
196 self.putFakeDeviceStatusesInCloudKit()
197 self.silentFetchesAllowed = true
198 }
199
200 self.cuttlefishContext.startOctagonStateMachine()
201 self.startCKAccountStatusMock()
202 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
203
204 // Before you call joinWithBottle, you need to call fetchViableBottles.
205 let fetchViableExpectation = self.expectation(description: "fetchViableBottles callback occurs")
206 self.cuttlefishContext.rpcFetchAllViableBottles { viable, _, error in
207 XCTAssertNil(error, "should be no error fetching viable bottles")
208 XCTAssert(viable?.contains(bottle.bottleID) ?? false, "The bottle we're about to restore should be viable")
209 fetchViableExpectation.fulfill()
210 }
211 self.wait(for: [fetchViableExpectation], timeout: 10)
212
213 let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs")
214 self.cuttlefishContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID!) { error in
215 XCTAssertNil(error, "error should be nil")
216 joinWithBottleExpectation.fulfill()
217 }
218
219 self.wait(for: [joinWithBottleExpectation], timeout: 100)
220
221 self.verifyDatabaseMocks()
222 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
223 assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLK, within: 10 * NSEC_PER_SEC)
224 }
225
226 func testBottleRestoreWithSameMachineID() throws {
227 self.startCKAccountStatusMock()
228
229 self.cuttlefishContext.startOctagonStateMachine()
230 XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled())
231 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
232
233 let clique: OTClique
234 do {
235 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
236 XCTAssertNotNil(clique, "Clique should not be nil")
237 } catch {
238 XCTFail("Shouldn't have errored making new friends: \(error)")
239 throw error
240 }
241
242 self.verifyDatabaseMocks()
243
244 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
245 XCTAssertNotNil(entropy, "entropy should not be nil")
246
247 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
248 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
249
250 let bottle = self.fakeCuttlefishServer.state.bottles[0]
251
252 // some other peer should restore from this bottle
253 let differentDevice = self.makeInitiatorContext(contextID: "differenDevice")
254 let differentRestoreExpectation = self.expectation(description: "different restore returns")
255 differentDevice.startOctagonStateMachine()
256 differentDevice.join(withBottle: bottle.bottleID,
257 entropy: entropy!,
258 bottleSalt: self.otcliqueContext.altDSID!) { error in
259 XCTAssertNil(error, "error should be nil")
260 differentRestoreExpectation.fulfill()
261 }
262 self.wait(for: [differentRestoreExpectation], timeout: 10)
263
264 self.assertTrusts(context: differentDevice, includedPeerIDCount: 2, excludedPeerIDCount: 0)
265
266 // The first peer will upload TLKs for the new peer
267 self.assertAllCKKSViewsUpload(tlkShares: 1)
268 self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext)
269 self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
270 self.verifyDatabaseMocks()
271
272 self.assertTrusts(context: self.cuttlefishContext, includedPeerIDCount: 2, excludedPeerIDCount: 0)
273
274 // Explicitly use the same authkit parameters as the original peer when restoring this time
275 let restoreContext = self.makeInitiatorContext(contextID: "restoreContext", authKitAdapter: self.mockAuthKit)
276
277 restoreContext.startOctagonStateMachine()
278
279 let restoreExpectation = self.expectation(description: "restore returns")
280 restoreContext.join(withBottle: bottle.bottleID,
281 entropy: entropy!,
282 bottleSalt: self.otcliqueContext.altDSID!) { error in
283 XCTAssertNil(error, "error should be nil")
284 restoreExpectation.fulfill()
285 }
286 self.wait(for: [restoreExpectation], timeout: 10)
287
288 self.assertEnters(context: restoreContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
289 self.assertConsidersSelfTrusted(context: restoreContext)
290
291 // The restore context should exclude its sponsor
292 self.assertTrusts(context: restoreContext, includedPeerIDCount: 2, excludedPeerIDCount: 1)
293
294 // Then, the remote peer should trust the new peer and exclude the original
295 self.sendContainerChangeWaitForFetchForStates(context: differentDevice, states: [OctagonStateReadyUpdated, OctagonStateReady])
296 self.assertTrusts(context: differentDevice, includedPeerIDCount: 2, excludedPeerIDCount: 1)
297
298 // Then, if by some strange miracle the original peer is still around, it should bail (as it's now untrusted)
299 self.sendContainerChangeWaitForFetchForStates(context: self.cuttlefishContext, states: [OctagonStateUntrusted])
300 self.assertConsidersSelfUntrusted(context: self.cuttlefishContext)
301 self.assertTrusts(context: self.cuttlefishContext, includedPeerIDCount: 0, excludedPeerIDCount: 1)
302 }
303
304 func testRestoreSPIFromPiggybackingState() throws {
305 self.startCKAccountStatusMock()
306
307 let initiatorContextID = "initiator-context-id"
308 self.cuttlefishContext.startOctagonStateMachine()
309 XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled())
310 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
311
312 let clique: OTClique
313 do {
314 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
315 XCTAssertNotNil(clique, "Clique should not be nil")
316 } catch {
317 XCTFail("Shouldn't have errored making new friends: \(error)")
318 throw error
319 }
320 self.verifyDatabaseMocks()
321
322 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
323 XCTAssertNotNil(entropy, "entropy should not be nil")
324
325 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
326 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
327
328 let bottle = self.fakeCuttlefishServer.state.bottles[0]
329
330 let initiatorContext = self.makeInitiatorContext(contextID: initiatorContextID)
331
332 initiatorContext.startOctagonStateMachine()
333
334 XCTAssertNoThrow(try initiatorContext.setCDPEnabled())
335 self.assertEnters(context: initiatorContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
336
337 let restoreExpectation = self.expectation(description: "restore returns")
338
339 self.manager!.restore(OTCKContainerName, contextID: initiatorContextID, bottleSalt: self.otcliqueContext.altDSID!, entropy: entropy!, bottleID: bottle.bottleID) { error in
340 XCTAssertNil(error, "error should be nil")
341 restoreExpectation.fulfill()
342 }
343 self.wait(for: [restoreExpectation], timeout: 10)
344
345 let initiatorDumpCallback = self.expectation(description: "initiatorDumpCallback callback occurs")
346 self.tphClient.dump(withContainer: OTCKContainerName, context: initiatorContextID) { dump, _ in
347 XCTAssertNotNil(dump, "dump should not be nil")
348 let egoSelf = dump!["self"] as? [String: AnyObject]
349 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
350 let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
351 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
352 let included = dynamicInfo!["included"] as? [String]
353 XCTAssertNotNil(included, "included should not be nil")
354 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
355
356 initiatorDumpCallback.fulfill()
357 }
358 self.wait(for: [initiatorDumpCallback], timeout: 10)
359 }
360
361 func testRestoreBadBottleIDFails() throws {
362 self.startCKAccountStatusMock()
363
364 let initiatorContextID = "initiator-context-id"
365 self.cuttlefishContext.startOctagonStateMachine()
366 XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled())
367 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
368
369 let clique: OTClique
370 do {
371 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
372 XCTAssertNotNil(clique, "Clique should not be nil")
373 } catch {
374 XCTFail("Shouldn't have errored making new friends: \(error)")
375 throw error
376 }
377
378 self.verifyDatabaseMocks()
379
380 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
381 XCTAssertNotNil(entropy, "entropy should not be nil")
382
383 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
384 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
385
386 _ = self.fakeCuttlefishServer.state.bottles[0]
387
388 let initiatorContext = self.makeInitiatorContext(contextID: initiatorContextID)
389
390 initiatorContext.startOctagonStateMachine()
391
392 let restoreExpectation = self.expectation(description: "restore returns")
393
394 self.manager!.restore(OTCKContainerName, contextID: initiatorContextID, bottleSalt: self.otcliqueContext.altDSID!, entropy: entropy!, bottleID: "bad escrow record ID") { error in
395 XCTAssertNotNil(error, "error should not be nil")
396 restoreExpectation.fulfill()
397 }
398 self.wait(for: [restoreExpectation], timeout: 10)
399 self.assertEnters(context: initiatorContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
400
401 let acceptorDumpCallback = self.expectation(description: "acceptorDumpCallback callback occurs")
402 self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) { dump, _ in
403 XCTAssertNotNil(dump, "dump should not be nil")
404 let egoSelf = dump!["self"] as? [String: AnyObject]
405 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
406 let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
407 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
408 let included = dynamicInfo!["included"] as? [String]
409 XCTAssertNotNil(included, "included should not be nil")
410 XCTAssertEqual(included!.count, 1, "should be 1 peer ids")
411
412 acceptorDumpCallback.fulfill()
413 }
414 self.wait(for: [acceptorDumpCallback], timeout: 10)
415 }
416
417 func testRestoreOptimalBottleIDs() throws {
418 self.startCKAccountStatusMock()
419
420 let clique: OTClique
421 do {
422 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
423 XCTAssertNotNil(clique, "Clique should not be nil")
424 } catch {
425 XCTFail("Shouldn't have errored making new friends: \(error)")
426 throw error
427 }
428
429 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
430 XCTAssertNotNil(entropy, "entropy should not be nil")
431
432 var bottleIDs = try OTClique.findOptimalBottleIDs(withContextData: self.otcliqueContext)
433 XCTAssertNotNil(bottleIDs.preferredBottleIDs, "preferredBottleIDs should not be nil")
434 XCTAssertEqual(bottleIDs.preferredBottleIDs.count, 1, "preferredBottleIDs should have 1 bottle")
435 XCTAssertEqual(bottleIDs.partialRecoveryBottleIDs.count, 0, "partialRecoveryBottleIDs should be empty")
436
437 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
438 self.verifyDatabaseMocks()
439 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
440
441 let bottle = self.fakeCuttlefishServer.state.bottles[0]
442
443 let initiatorContext = self.manager.context(forContainerName: OTCKContainerName,
444 contextID: "restoreContext",
445 sosAdapter: OTSOSMissingAdapter(),
446 authKitAdapter: self.mockAuthKit2,
447 lockStateTracker: self.lockStateTracker,
448 accountStateTracker: self.accountStateTracker,
449 deviceInformationAdapter: self.makeInitiatorDeviceInfoAdapter())
450
451 initiatorContext.startOctagonStateMachine()
452 let newOTCliqueContext = OTConfigurationContext()
453 newOTCliqueContext.context = "restoreContext"
454 newOTCliqueContext.dsid = self.otcliqueContext.dsid
455 newOTCliqueContext.altDSID = self.otcliqueContext.altDSID
456 newOTCliqueContext.otControl = self.otcliqueContext.otControl
457 newOTCliqueContext.sbd = OTMockSecureBackup(bottleID: bottle.bottleID, entropy: entropy!)
458
459 let newClique: OTClique
460 do {
461 newClique = try OTClique.performEscrowRecovery(withContextData: newOTCliqueContext, escrowArguments: [:])
462 XCTAssertNotNil(newClique, "newClique should not be nil")
463 } catch {
464 XCTFail("Shouldn't have errored recovering: \(error)")
465 throw error
466 }
467
468 // We will upload a new TLK for the new peer
469 self.assertAllCKKSViewsUpload(tlkShares: 1)
470 self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext)
471 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
472
473 self.sendContainerChangeWaitForFetch(context: initiatorContext)
474 bottleIDs = try OTClique.findOptimalBottleIDs(withContextData: self.otcliqueContext)
475 XCTAssertNotNil(bottleIDs.preferredBottleIDs, "preferredBottleIDs should not be nil")
476 XCTAssertEqual(bottleIDs.preferredBottleIDs.count, 2, "preferredBottleIDs should have 2 bottle")
477 XCTAssertEqual(bottleIDs.partialRecoveryBottleIDs.count, 0, "partialRecoveryBottleIDs should be empty")
478 }
479
480 func testRestoreFromEscrowContents() throws {
481 self.startCKAccountStatusMock()
482
483 let clique: OTClique
484 do {
485 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
486 XCTAssertNotNil(clique, "Clique should not be nil")
487 } catch {
488 XCTFail("Shouldn't have errored making new friends: \(error)")
489 throw error
490 }
491
492 var bottleIDs = try OTClique.findOptimalBottleIDs(withContextData: self.otcliqueContext)
493 XCTAssertNotNil(bottleIDs.preferredBottleIDs, "preferredBottleIDs should not be nil")
494 XCTAssertEqual(bottleIDs.preferredBottleIDs.count, 1, "preferredBottleIDs should have 1 bottle")
495 XCTAssertEqual(bottleIDs.partialRecoveryBottleIDs.count, 0, "partialRecoveryBottleIDs should be empty")
496
497 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
498 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
499 self.verifyDatabaseMocks()
500
501 var entropy = Data()
502 var bottledID: String = ""
503
504 let fetchEscrowContentsExpectation = self.expectation(description: "fetchEscrowContentsExpectation returns")
505 clique.fetchEscrowContents { e, b, s, _ in
506 XCTAssertNotNil(e, "entropy should not be nil")
507 XCTAssertNotNil(b, "bottleID should not be nil")
508 XCTAssertNotNil(s, "signingPublicKey should not be nil")
509 entropy = e!
510 bottledID = b!
511 fetchEscrowContentsExpectation.fulfill()
512 }
513
514 self.wait(for: [fetchEscrowContentsExpectation], timeout: 10)
515
516 let initiatorContext = self.manager.context(forContainerName: OTCKContainerName,
517 contextID: "restoreContext",
518 sosAdapter: OTSOSMissingAdapter(),
519 authKitAdapter: self.mockAuthKit2,
520 lockStateTracker: self.lockStateTracker,
521 accountStateTracker: self.accountStateTracker,
522 deviceInformationAdapter: self.makeInitiatorDeviceInfoAdapter())
523
524 initiatorContext.startOctagonStateMachine()
525 let newOTCliqueContext = OTConfigurationContext()
526 newOTCliqueContext.context = "restoreContext"
527 newOTCliqueContext.dsid = self.otcliqueContext.dsid
528 newOTCliqueContext.altDSID = self.otcliqueContext.altDSID
529 newOTCliqueContext.otControl = self.otcliqueContext.otControl
530 newOTCliqueContext.sbd = OTMockSecureBackup(bottleID: bottledID, entropy: entropy)
531
532 let newClique: OTClique
533 do {
534 newClique = try OTClique.performEscrowRecovery(withContextData: newOTCliqueContext, escrowArguments: [:])
535 XCTAssertNotNil(newClique, "newClique should not be nil")
536 } catch {
537 XCTFail("Shouldn't have errored recovering: \(error)")
538 throw error
539 }
540
541 // We will upload a new TLK for the new peer
542 self.assertAllCKKSViewsUpload(tlkShares: 1)
543 self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext)
544 self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
545 self.verifyDatabaseMocks()
546
547 self.sendContainerChangeWaitForFetch(context: initiatorContext)
548
549 bottleIDs = try OTClique.findOptimalBottleIDs(withContextData: self.otcliqueContext)
550 XCTAssertNotNil(bottleIDs.preferredBottleIDs, "preferredBottleIDs should not be nil")
551 XCTAssertEqual(bottleIDs.preferredBottleIDs.count, 2, "preferredBottleIDs should have 2 bottle")
552 XCTAssertEqual(bottleIDs.partialRecoveryBottleIDs.count, 0, "partialRecoveryBottleIDs should be empty")
553
554 let dumpExpectation = self.expectation(description: "dump callback occurs")
555 self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) { dump, error in
556 XCTAssertNil(error, "Should be no error dumping data")
557 XCTAssertNotNil(dump, "dump should not be nil")
558 let egoSelf = dump!["self"] as? [String: AnyObject]
559 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
560 let peerID = egoSelf!["peerID"] as? String
561 XCTAssertNotNil(peerID, "peerID should not be nil")
562
563 dumpExpectation.fulfill()
564 }
565 self.wait(for: [dumpExpectation], timeout: 10)
566
567 self.otControlCLI.status(OTCKContainerName,
568 context: newOTCliqueContext.context,
569 json: false)
570 }
571
572 func testFetchEmptyOptimalBottleList () throws {
573 self.startCKAccountStatusMock()
574
575 let bottleIDs = try OTClique.findOptimalBottleIDs(withContextData: self.otcliqueContext)
576 XCTAssertEqual(bottleIDs.preferredBottleIDs.count, 0, "should be 0 preferred bottle ids")
577 XCTAssertEqual(bottleIDs.partialRecoveryBottleIDs.count, 0, "should be 0 partialRecoveryBottleIDs")
578 }
579
580 func testFetchOptimalBottlesAfterFailedRestore() throws {
581 self.startCKAccountStatusMock()
582
583 self.otcliqueContext.sbd = OTMockSecureBackup(bottleID: "bottle ID", entropy: Data(count: 72))
584
585 XCTAssertThrowsError(try OTClique.performEscrowRecovery(withContextData: self.otcliqueContext, escrowArguments: [:]))
586
587 let bottleIDs = try OTClique.findOptimalBottleIDs(withContextData: self.otcliqueContext)
588 XCTAssertNotNil(bottleIDs.preferredBottleIDs, "preferredBottleIDs should not be nil")
589 XCTAssertEqual(bottleIDs.preferredBottleIDs.count, 0, "preferredBottleIDs should have 0 bottle")
590 XCTAssertEqual(bottleIDs.partialRecoveryBottleIDs.count, 0, "partialRecoveryBottleIDs should be empty")
591 }
592
593 func testMakeNewFriendsAndFetchEscrowContents () throws {
594 self.startCKAccountStatusMock()
595
596 let clique: OTClique
597 do {
598 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
599 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
600 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
601
602 let fetchEscrowContentsException = self.expectation(description: "update returns")
603
604 clique.fetchEscrowContents { e, b, s, _ in
605 XCTAssertNotNil(e, "entropy should not be nil")
606 XCTAssertNotNil(b, "bottleID should not be nil")
607 XCTAssertNotNil(s, "signingPublicKey should not be nil")
608 fetchEscrowContentsException.fulfill()
609 }
610 self.wait(for: [fetchEscrowContentsException], timeout: 10)
611
612 } catch {
613 XCTFail("failed to reset clique: \(error)")
614 }
615
616 self.verifyDatabaseMocks()
617 }
618
619 func testFetchEscrowContentsBeforeIdentityExists() {
620 self.startCKAccountStatusMock()
621
622 let initiatorContext = self.manager.context(forContainerName: OTCKContainerName, contextID: "initiator")
623 let fetchEscrowContentsException = self.expectation(description: "update returns")
624
625 initiatorContext.fetchEscrowContents { entropy, bottleID, signingPubKey, error in
626 XCTAssertNil(entropy, "entropy should be nil")
627 XCTAssertNil(bottleID, "bottleID should be nil")
628 XCTAssertNil(signingPubKey, "signingPublicKey should be nil")
629 XCTAssertNotNil(error, "error should not be nil")
630
631 fetchEscrowContentsException.fulfill()
632 }
633 self.wait(for: [fetchEscrowContentsException], timeout: 10)
634 }
635
636 func testFetchEscrowContentsChecksEntitlement() throws {
637 self.startCKAccountStatusMock()
638
639 let contextName = OTDefaultContext
640 let containerName = OTCKContainerName
641
642 // First, fail due to not having any data
643 let fetchEscrowContentsExpectation = self.expectation(description: "fetchEscrowContentsExpectation returns")
644 self.otControl.fetchEscrowContents(containerName, contextID: contextName) { entropy, bottle, signingPublicKey, error in
645 XCTAssertNotNil(error, "error should not be nil")
646 //XCTAssertNotEqual(error.code, errSecMissingEntitlement, "Error should not be 'missing entitlement'")
647 XCTAssertNil(entropy, "entropy should be nil")
648 XCTAssertNil(bottle, "bottleID should be nil")
649 XCTAssertNil(signingPublicKey, "signingPublicKey should be nil")
650 fetchEscrowContentsExpectation.fulfill()
651 }
652 self.wait(for: [fetchEscrowContentsExpectation], timeout: 10)
653
654 // Now, fail due to the client not having an entitlement
655 self.otControlEntitlementBearer.entitlements.removeAll()
656 let failFetchEscrowContentsExpectation = self.expectation(description: "fetchEscrowContentsExpectation returns")
657 self.otControl.fetchEscrowContents(containerName, contextID: contextName) { entropy, bottle, signingPublicKey, error in
658 XCTAssertNotNil(error, "error should not be nil")
659 switch error {
660 case .some(let error as NSError):
661 XCTAssertEqual(error.domain, NSOSStatusErrorDomain, "Error should be an OS status")
662 XCTAssertEqual(error.code, Int(errSecMissingEntitlement), "Error should be 'missing entitlement'")
663 default:
664 XCTFail("Unable to turn error into NSError: \(String(describing: error))")
665 }
666
667 XCTAssertNil(entropy, "entropy should not be nil")
668 XCTAssertNil(bottle, "bottleID should not be nil")
669 XCTAssertNil(signingPublicKey, "signingPublicKey should not be nil")
670 failFetchEscrowContentsExpectation.fulfill()
671 }
672 self.wait(for: [failFetchEscrowContentsExpectation], timeout: 10)
673 }
674
675 func testJoinWithBottleFailCaseBottleDoesntExist() throws {
676 self.startCKAccountStatusMock()
677
678 let initiatorContextID = "initiator-context-id"
679 self.cuttlefishContext.startOctagonStateMachine()
680 XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled())
681 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
682
683 let clique: OTClique
684 do {
685 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
686 XCTAssertNotNil(clique, "Clique should not be nil")
687 } catch {
688 XCTFail("Shouldn't have errored making new friends: \(error)")
689 throw error
690 }
691
692 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
693 XCTAssertNotNil(entropy, "entropy should not be nil")
694
695 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
696 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
697 self.verifyDatabaseMocks()
698
699 let bottle = self.fakeCuttlefishServer.state.bottles[0]
700 self.fakeCuttlefishServer.state.bottles.removeAll()
701
702 let initiatorContext = self.makeInitiatorContext(contextID: initiatorContextID)
703
704 initiatorContext.startOctagonStateMachine()
705
706 let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs")
707 initiatorContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID!) { error in
708 XCTAssertNotNil(error, "error should not be nil")
709 joinWithBottleExpectation.fulfill()
710 }
711 self.wait(for: [joinWithBottleExpectation], timeout: 10)
712 self.assertEnters(context: initiatorContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
713 }
714
715 func testJoinWithBottleFailCaseBadEscrowRecord() throws {
716 self.startCKAccountStatusMock()
717
718 let initiatorContextID = "initiator-context-id"
719
720 self.cuttlefishContext.startOctagonStateMachine()
721 XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled())
722 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
723
724 let clique: OTClique
725 do {
726 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
727 XCTAssertNotNil(clique, "Clique should not be nil")
728 } catch {
729 XCTFail("Shouldn't have errored making new friends: \(error)")
730 throw error
731 }
732
733 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
734 XCTAssertNotNil(entropy, "entropy should not be nil")
735
736 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
737 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
738 self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
739 self.verifyDatabaseMocks()
740
741 _ = self.fakeCuttlefishServer.state.bottles[0]
742
743 let initiatorContext = self.makeInitiatorContext(contextID: initiatorContextID)
744
745 initiatorContext.startOctagonStateMachine()
746
747 let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs")
748 initiatorContext.join(withBottle: "sos peer id", entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID!) { error in
749 XCTAssertNotNil(error, "error should be nil")
750 joinWithBottleExpectation.fulfill()
751 }
752 self.wait(for: [joinWithBottleExpectation], timeout: 10)
753 self.assertEnters(context: initiatorContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
754 }
755
756 func testJoinWithBottleFailCaseBadEntropy() throws {
757 self.startCKAccountStatusMock()
758
759 let initiatorContextID = "initiator-context-id"
760 self.cuttlefishContext.startOctagonStateMachine()
761 XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled())
762 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
763
764 let clique: OTClique
765 do {
766 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
767 XCTAssertNotNil(clique, "Clique should not be nil")
768 } catch {
769 XCTFail("Shouldn't have errored making new friends: \(error)")
770 throw error
771 }
772
773 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
774 XCTAssertNotNil(entropy, "entropy should not be nil")
775
776 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
777 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
778 self.verifyDatabaseMocks()
779
780 let bottle = self.fakeCuttlefishServer.state.bottles[0]
781
782 let initiatorContext = self.makeInitiatorContext(contextID: initiatorContextID)
783
784 initiatorContext.startOctagonStateMachine()
785
786 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
787
788 let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs")
789 initiatorContext.join(withBottle: bottle.bottleID, entropy: Data(count: 72), bottleSalt: self.otcliqueContext.altDSID!) { error in
790 XCTAssertNotNil(error, "error should not be nil, when entropy is missing")
791 joinWithBottleExpectation.fulfill()
792 }
793 self.wait(for: [joinWithBottleExpectation], timeout: 10)
794 self.assertEnters(context: initiatorContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
795 }
796
797 func testJoinWithBottleFailCaseBadBottleSalt() throws {
798 self.startCKAccountStatusMock()
799
800 let initiatorContextID = "initiator-context-id"
801 self.cuttlefishContext.startOctagonStateMachine()
802 XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled())
803 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
804
805 let clique: OTClique
806 do {
807 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
808 XCTAssertNotNil(clique, "Clique should not be nil")
809 } catch {
810 XCTFail("Shouldn't have errored making new friends: \(error)")
811 throw error
812 }
813
814 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
815 XCTAssertNotNil(entropy, "entropy should not be nil")
816
817 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
818 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
819 self.verifyDatabaseMocks()
820
821 let bottle = self.fakeCuttlefishServer.state.bottles[0]
822
823 let initiatorContext = self.makeInitiatorContext(contextID: initiatorContextID)
824
825 initiatorContext.startOctagonStateMachine()
826
827 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
828
829 let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs")
830 initiatorContext.join(withBottle: bottle.bottleID, entropy: Data(count: 72), bottleSalt: "123456789") { error in
831 XCTAssertNotNil(error, "error should not be nil with bad entropy and salt")
832 joinWithBottleExpectation.fulfill()
833 }
834 self.wait(for: [joinWithBottleExpectation], timeout: 10)
835 self.assertEnters(context: initiatorContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
836 }
837
838 func testRecoverFromDeviceNotOnMachineIDList() throws {
839 self.startCKAccountStatusMock()
840
841 let clique: OTClique
842 do {
843 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
844 XCTAssertNotNil(clique, "Clique should not be nil")
845 } catch {
846 XCTFail("Shouldn't have errored making new friends: \(error)")
847 throw error
848 }
849
850 let firstPeerID = clique.cliqueMemberIdentifier
851 XCTAssertNotNil(firstPeerID, "Clique should have a member identifier")
852 let entropy = try self.loadSecret(label: firstPeerID!)
853 XCTAssertNotNil(entropy, "entropy should not be nil")
854
855 let bottleIDs = try OTClique.findOptimalBottleIDs(withContextData: self.otcliqueContext)
856 XCTAssertNotNil(bottleIDs.preferredBottleIDs, "preferredBottleIDs should not be nil")
857 XCTAssertEqual(bottleIDs.preferredBottleIDs.count, 1, "preferredBottleIDs should have 1 bottle")
858 XCTAssertEqual(bottleIDs.partialRecoveryBottleIDs.count, 0, "partialRecoveryBottleIDs should be empty")
859
860 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
861 self.verifyDatabaseMocks()
862 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
863
864 let bottle = self.fakeCuttlefishServer.state.bottles[0]
865
866 // To get into the state we need, we need to introduce peer B and C. C should then distrust A, whose bottle it used
867 // B shouldn't have an opinion of C.
868
869 let bNewOTCliqueContext = OTConfigurationContext()
870 bNewOTCliqueContext.context = "restoreB"
871 bNewOTCliqueContext.dsid = self.otcliqueContext.dsid
872 bNewOTCliqueContext.altDSID = self.otcliqueContext.altDSID
873 bNewOTCliqueContext.otControl = self.otcliqueContext.otControl
874 bNewOTCliqueContext.sbd = OTMockSecureBackup(bottleID: bottle.bottleID, entropy: entropy!)
875
876 let deviceBmockAuthKit = OTMockAuthKitAdapter(altDSID: self.otcliqueContext.altDSID!,
877 machineID: "b-machine-id",
878 otherDevices: [self.mockAuthKit.currentMachineID])
879
880 let bRestoreContext = self.manager.context(forContainerName: OTCKContainerName,
881 contextID: bNewOTCliqueContext.context,
882 sosAdapter: OTSOSMissingAdapter(),
883 authKitAdapter: deviceBmockAuthKit,
884 lockStateTracker: self.lockStateTracker,
885 accountStateTracker: self.accountStateTracker,
886 deviceInformationAdapter: self.makeInitiatorDeviceInfoAdapter())
887 bRestoreContext.startOctagonStateMachine()
888 let bNewClique: OTClique
889 do {
890 bNewClique = try OTClique.performEscrowRecovery(withContextData: bNewOTCliqueContext, escrowArguments: [:])
891 XCTAssertNotNil(bNewClique, "bNewClique should not be nil")
892 } catch {
893 XCTFail("Shouldn't have errored recovering: \(error)")
894 throw error
895 }
896 self.assertEnters(context: bRestoreContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
897
898 // And introduce C, which will kick out A
899 // During the next sign in, the machine ID list has changed to just the new one
900 let restoremockAuthKit = OTMockAuthKitAdapter(altDSID: self.otcliqueContext.altDSID!,
901 machineID: "c-machine-id",
902 otherDevices: [self.mockAuthKit.currentMachineID, deviceBmockAuthKit.currentMachineID])
903 let restoreContext = self.manager.context(forContainerName: OTCKContainerName,
904 contextID: "restoreContext",
905 sosAdapter: OTSOSMissingAdapter(),
906 authKitAdapter: restoremockAuthKit,
907 lockStateTracker: self.lockStateTracker,
908 accountStateTracker: self.accountStateTracker,
909 deviceInformationAdapter: self.makeInitiatorDeviceInfoAdapter())
910
911 restoreContext.startOctagonStateMachine()
912 let newOTCliqueContext = OTConfigurationContext()
913 newOTCliqueContext.context = "restoreContext"
914 newOTCliqueContext.dsid = self.otcliqueContext.dsid
915 newOTCliqueContext.altDSID = self.otcliqueContext.altDSID
916 newOTCliqueContext.otControl = self.otcliqueContext.otControl
917 newOTCliqueContext.sbd = OTMockSecureBackup(bottleID: bottle.bottleID, entropy: entropy!)
918
919 let newClique: OTClique
920 do {
921 newClique = try OTClique.performEscrowRecovery(withContextData: newOTCliqueContext, escrowArguments: [:])
922 XCTAssertNotNil(newClique, "newClique should not be nil")
923 } catch {
924 XCTFail("Shouldn't have errored recovering: \(error)")
925 throw error
926 }
927
928 self.assertEnters(context: restoreContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
929 self.verifyDatabaseMocks()
930 self.assertConsidersSelfTrusted(context: restoreContext)
931
932 let restoreDumpCallback = self.expectation(description: "acceptorDumpCallback callback occurs")
933 self.tphClient.dump(withContainer: OTCKContainerName, context: newOTCliqueContext.context) { dump, _ in
934 XCTAssertNotNil(dump, "dump should not be nil")
935 let egoSelf = dump!["self"] as? [String: AnyObject]
936 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
937 let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
938 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
939 let included = dynamicInfo!["included"] as? [String]
940 XCTAssertNotNil(included, "included should not be nil")
941 XCTAssertEqual(included!.count, 3, "should be 3 peer id included")
942
943 restoreDumpCallback.fulfill()
944 }
945 self.wait(for: [restoreDumpCallback], timeout: 10)
946
947 // Now, exclude peer A's machine ID
948 restoremockAuthKit.otherDevices = [deviceBmockAuthKit.currentMachineID]
949
950 // Peer C should upload a new trust status
951 let updateTrustExpectation = self.expectation(description: "updateTrust")
952 self.fakeCuttlefishServer.updateListener = { request in
953 XCTAssertTrue(request.hasDynamicInfoAndSig, "updateTrust request should have a dynamic info")
954 let newDynamicInfo = TPPeerDynamicInfo(data: request.dynamicInfoAndSig.peerDynamicInfo,
955 sig: request.dynamicInfoAndSig.sig)
956 XCTAssertNotNil(newDynamicInfo, "should be able to make a dynamic info from protobuf")
957
958 XCTAssertEqual(newDynamicInfo!.excludedPeerIDs.count, 1, "Should have a single excluded peer")
959 updateTrustExpectation.fulfill()
960 return nil
961 }
962
963 restoreContext.incompleteNotificationOfMachineIDListChange()
964 self.wait(for: [updateTrustExpectation], timeout: 10)
965
966 self.assertEnters(context: restoreContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
967 self.verifyDatabaseMocks()
968 self.assertConsidersSelfTrusted(context: restoreContext)
969 }
970
971 func testCachedBottleFetch() throws {
972 let initiatorContextID = "initiator-context-id"
973 let bottlerContext = self.makeInitiatorContext(contextID: initiatorContextID)
974
975 bottlerContext.startOctagonStateMachine()
976 let ckacctinfo = CKAccountInfo()
977 ckacctinfo.accountStatus = .available
978 ckacctinfo.hasValidCredentials = true
979 ckacctinfo.accountPartition = .production
980
981 bottlerContext.cloudkitAccountStateChange(nil, to: ckacctinfo)
982 XCTAssertNoThrow(try bottlerContext.setCDPEnabled())
983 self.assertEnters(context: bottlerContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
984
985 let clique: OTClique
986 let bottlerotcliqueContext = OTConfigurationContext()
987 bottlerotcliqueContext.context = initiatorContextID
988 bottlerotcliqueContext.dsid = "1234"
989 bottlerotcliqueContext.altDSID = self.mockAuthKit.altDSID!
990 bottlerotcliqueContext.otControl = self.otControl
991 do {
992 clique = try OTClique.newFriends(withContextData: bottlerotcliqueContext, resetReason: .testGenerated)
993 XCTAssertNotNil(clique, "Clique should not be nil")
994 XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call")
995 } catch {
996 XCTFail("Shouldn't have errored making new friends: \(error)")
997 throw error
998 }
999
1000 self.assertEnters(context: bottlerContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
1001 self.assertConsidersSelfTrusted(context: bottlerContext)
1002
1003 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
1004 XCTAssertNotNil(entropy, "entropy should not be nil")
1005
1006 // Fake that this peer also created some TLKShares for itself
1007 self.putFakeKeyHierarchiesInCloudKit()
1008 try self.putSelfTLKSharesInCloudKit(context: bottlerContext)
1009
1010 let bottle = self.fakeCuttlefishServer.state.bottles[0]
1011
1012 self.cuttlefishContext.startOctagonStateMachine()
1013 self.startCKAccountStatusMock()
1014 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
1015
1016 // Try to enforce that that CKKS doesn't know about the key hierarchy until Octagon asks it
1017 self.holdCloudKitFetches()
1018
1019 // Note: CKKS will want to upload a TLKShare for its self
1020 self.assertAllCKKSViewsUpload(tlkShares: 1)
1021
1022 let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs")
1023 self.cuttlefishContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID!) { error in
1024 XCTAssertNil(error, "error should be nil")
1025 joinWithBottleExpectation.fulfill()
1026 }
1027
1028 sleep(1)
1029 self.releaseCloudKitFetchHold()
1030
1031 self.wait(for: [joinWithBottleExpectation], timeout: 100)
1032
1033 let dumpCallback = self.expectation(description: "dumpCallback callback occurs")
1034 self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) { dump, _ in
1035 XCTAssertNotNil(dump, "dump should not be nil")
1036 let egoSelf = dump!["self"] as? [String: AnyObject]
1037 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
1038 let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
1039 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
1040 let included = dynamicInfo!["included"] as? [String]
1041 XCTAssertNotNil(included, "included should not be nil")
1042 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
1043 dumpCallback.fulfill()
1044 }
1045 self.wait(for: [dumpCallback], timeout: 10)
1046
1047 self.verifyDatabaseMocks()
1048 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
1049 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
1050
1051 //now call fetchviablebottles, we should get the uncached version
1052 let fetchUnCachedViableBottlesExpectation = self.expectation(description: "fetch UnCached ViableBottles")
1053
1054 self.fakeCuttlefishServer.fetchViableBottlesListener = { request in
1055 self.fakeCuttlefishServer.fetchViableBottlesListener = nil
1056 fetchUnCachedViableBottlesExpectation.fulfill()
1057 return nil
1058 }
1059 let FetchAllViableBottles = self.expectation(description: "FetchAllViableBottles callback occurs")
1060
1061 self.cuttlefishContext.rpcFetchAllViableBottles { viable, _, error in
1062 XCTAssertNil(error, "should be no error fetching viable bottles")
1063 XCTAssert(viable?.contains(bottle.bottleID) ?? false, "The bottle we're about to restore should be viable")
1064 FetchAllViableBottles.fulfill()
1065 }
1066 self.wait(for: [FetchAllViableBottles], timeout: 10)
1067 self.wait(for: [fetchUnCachedViableBottlesExpectation], timeout: 10)
1068
1069 let fetchViableExpectation = self.expectation(description: "fetchViableBottles callback occurs")
1070 self.cuttlefishContext.rpcFetchAllViableBottles { viable, _, error in
1071 XCTAssertNil(error, "should be no error fetching viable bottles")
1072 XCTAssert(viable?.contains(bottle.bottleID) ?? false, "The bottle we're about to restore should be viable")
1073 fetchViableExpectation.fulfill()
1074 }
1075 self.wait(for: [fetchViableExpectation], timeout: 10)
1076
1077 //now call fetchviablebottles, we should get the cached version
1078 let fetchViableBottlesExpectation = self.expectation(description: "fetch Cached ViableBottles")
1079 fetchViableBottlesExpectation.isInverted = true
1080
1081 self.fakeCuttlefishServer.fetchViableBottlesListener = { request in
1082 self.fakeCuttlefishServer.fetchViableBottlesListener = nil
1083 fetchViableBottlesExpectation.fulfill()
1084 return nil
1085 }
1086 let fetchExpectation = self.expectation(description: "fetchExpectation callback occurs")
1087
1088 self.cuttlefishContext.rpcFetchAllViableBottles { viable, _, error in
1089 XCTAssertNil(error, "should be no error fetching viable bottles")
1090 XCTAssert(viable?.contains(bottle.bottleID) ?? false, "The bottle we're about to restore should be viable")
1091 fetchExpectation.fulfill()
1092 }
1093 self.wait(for: [fetchExpectation], timeout: 10)
1094 self.wait(for: [fetchViableBottlesExpectation], timeout: 10)
1095 }
1096
1097 func testViableBottleCachingAfterJoin() throws {
1098 let initiatorContextID = "initiator-context-id"
1099 let bottlerContext = self.makeInitiatorContext(contextID: initiatorContextID)
1100
1101 bottlerContext.startOctagonStateMachine()
1102 let ckacctinfo = CKAccountInfo()
1103 ckacctinfo.accountStatus = .available
1104 ckacctinfo.hasValidCredentials = true
1105 ckacctinfo.accountPartition = .production
1106
1107 bottlerContext.cloudkitAccountStateChange(nil, to: ckacctinfo)
1108 XCTAssertNoThrow(try bottlerContext.setCDPEnabled())
1109 self.assertEnters(context: bottlerContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
1110
1111 let clique: OTClique
1112 let bottlerotcliqueContext = OTConfigurationContext()
1113 bottlerotcliqueContext.context = initiatorContextID
1114 bottlerotcliqueContext.dsid = "1234"
1115 bottlerotcliqueContext.altDSID = self.mockAuthKit.altDSID!
1116 bottlerotcliqueContext.otControl = self.otControl
1117 do {
1118 clique = try OTClique.newFriends(withContextData: bottlerotcliqueContext, resetReason: .testGenerated)
1119 XCTAssertNotNil(clique, "Clique should not be nil")
1120 XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call")
1121 } catch {
1122 XCTFail("Shouldn't have errored making new friends: \(error)")
1123 throw error
1124 }
1125
1126 self.assertEnters(context: bottlerContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
1127 self.assertConsidersSelfTrusted(context: bottlerContext)
1128
1129 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
1130 XCTAssertNotNil(entropy, "entropy should not be nil")
1131
1132 // Fake that this peer also created some TLKShares for itself
1133 self.putFakeKeyHierarchiesInCloudKit()
1134 try self.putSelfTLKSharesInCloudKit(context: bottlerContext)
1135
1136 let bottle = self.fakeCuttlefishServer.state.bottles[0]
1137
1138 self.cuttlefishContext.startOctagonStateMachine()
1139 self.startCKAccountStatusMock()
1140 XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled())
1141 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
1142
1143 // Try to enforce that that CKKS doesn't know about the key hierarchy until Octagon asks it
1144 self.holdCloudKitFetches()
1145
1146 // Note: CKKS will want to upload a TLKShare for its self
1147 self.assertAllCKKSViewsUpload(tlkShares: 1)
1148
1149 let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs")
1150 self.cuttlefishContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID!) { error in
1151 XCTAssertNil(error, "error should be nil")
1152 joinWithBottleExpectation.fulfill()
1153 }
1154
1155 sleep(1)
1156 self.releaseCloudKitFetchHold()
1157
1158 self.wait(for: [joinWithBottleExpectation], timeout: 100)
1159
1160 let dumpCallback = self.expectation(description: "dumpCallback callback occurs")
1161 self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) { dump, _ in
1162 XCTAssertNotNil(dump, "dump should not be nil")
1163 let egoSelf = dump!["self"] as? [String: AnyObject]
1164 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
1165 let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
1166 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
1167 let included = dynamicInfo!["included"] as? [String]
1168 XCTAssertNotNil(included, "included should not be nil")
1169 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
1170 dumpCallback.fulfill()
1171 }
1172 self.wait(for: [dumpCallback], timeout: 10)
1173
1174 self.verifyDatabaseMocks()
1175 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
1176 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
1177
1178 //now call fetchviablebottles, we should get the uncached version
1179 let fetchUnCachedViableBottlesExpectation = self.expectation(description: "fetch UnCached ViableBottles")
1180
1181 self.fakeCuttlefishServer.fetchViableBottlesListener = { request in
1182 self.fakeCuttlefishServer.fetchViableBottlesListener = nil
1183 fetchUnCachedViableBottlesExpectation.fulfill()
1184 return nil
1185 }
1186 let FetchAllViableBottles = self.expectation(description: "FetchAllViableBottles callback occurs")
1187
1188 self.cuttlefishContext.rpcFetchAllViableBottles { viable, _, error in
1189 XCTAssertNil(error, "should be no error fetching viable bottles")
1190 XCTAssert(viable?.contains(bottle.bottleID) ?? false, "The bottle we're about to restore should be viable")
1191 FetchAllViableBottles.fulfill()
1192 }
1193 self.wait(for: [FetchAllViableBottles], timeout: 10)
1194 self.wait(for: [fetchUnCachedViableBottlesExpectation], timeout: 10)
1195
1196 let fetchViableExpectation = self.expectation(description: "fetchViableBottles callback occurs")
1197 self.cuttlefishContext.rpcFetchAllViableBottles { viable, _, error in
1198 XCTAssertNil(error, "should be no error fetching viable bottles")
1199 XCTAssert(viable?.contains(bottle.bottleID) ?? false, "The bottle we're about to restore should be viable")
1200 fetchViableExpectation.fulfill()
1201 }
1202 self.wait(for: [fetchViableExpectation], timeout: 10)
1203
1204 //now call fetchviablebottles, we should get the cached version
1205 let fetchViableBottlesExpectation = self.expectation(description: "fetch Cached ViableBottles")
1206 fetchViableBottlesExpectation.isInverted = true
1207
1208 self.fakeCuttlefishServer.fetchViableBottlesListener = { request in
1209 self.fakeCuttlefishServer.fetchViableBottlesListener = nil
1210 fetchViableBottlesExpectation.fulfill()
1211 return nil
1212 }
1213 let fetchExpectation = self.expectation(description: "fetchExpectation callback occurs")
1214
1215 self.cuttlefishContext.rpcFetchAllViableBottles { viable, _, error in
1216 XCTAssertNil(error, "should be no error fetching viable bottles")
1217 XCTAssert(viable?.contains(bottle.bottleID) ?? false, "The bottle we're about to restore should be viable")
1218 fetchExpectation.fulfill()
1219 }
1220 self.wait(for: [fetchExpectation], timeout: 10)
1221 self.wait(for: [fetchViableBottlesExpectation], timeout: 10)
1222 }
1223
1224 func testViableBottleReturns1Bottle() throws {
1225 let initiatorContextID = "initiator-context-id"
1226 let bottlerContext = self.makeInitiatorContext(contextID: initiatorContextID)
1227
1228 bottlerContext.startOctagonStateMachine()
1229 let ckacctinfo = CKAccountInfo()
1230 ckacctinfo.accountStatus = .available
1231 ckacctinfo.hasValidCredentials = true
1232 ckacctinfo.accountPartition = .production
1233
1234 bottlerContext.cloudkitAccountStateChange(nil, to: ckacctinfo)
1235 XCTAssertNoThrow(try bottlerContext.setCDPEnabled())
1236 self.assertEnters(context: bottlerContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
1237
1238 let clique: OTClique
1239 let bottlerotcliqueContext = OTConfigurationContext()
1240 bottlerotcliqueContext.context = initiatorContextID
1241 bottlerotcliqueContext.dsid = "1234"
1242 bottlerotcliqueContext.altDSID = self.mockAuthKit.altDSID!
1243 bottlerotcliqueContext.otControl = self.otControl
1244 do {
1245 clique = try OTClique.newFriends(withContextData: bottlerotcliqueContext, resetReason: .testGenerated)
1246 XCTAssertNotNil(clique, "Clique should not be nil")
1247 XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call")
1248 } catch {
1249 XCTFail("Shouldn't have errored making new friends: \(error)")
1250 throw error
1251 }
1252
1253 self.assertEnters(context: bottlerContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
1254 self.assertConsidersSelfTrusted(context: bottlerContext)
1255
1256 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
1257 XCTAssertNotNil(entropy, "entropy should not be nil")
1258
1259 // Fake that this peer also created some TLKShares for itself
1260 self.putFakeKeyHierarchiesInCloudKit()
1261 try self.putSelfTLKSharesInCloudKit(context: bottlerContext)
1262
1263 let bottle = self.fakeCuttlefishServer.state.bottles[0]
1264
1265 self.cuttlefishContext.startOctagonStateMachine()
1266 self.startCKAccountStatusMock()
1267 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
1268
1269 // Try to enforce that that CKKS doesn't know about the key hierarchy until Octagon asks it
1270 self.holdCloudKitFetches()
1271
1272 // Note: CKKS will want to upload a TLKShare for its self
1273 self.assertAllCKKSViewsUpload(tlkShares: 1)
1274
1275 let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs")
1276 self.cuttlefishContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID!) { error in
1277 XCTAssertNil(error, "error should be nil")
1278 joinWithBottleExpectation.fulfill()
1279 }
1280
1281 sleep(1)
1282 self.releaseCloudKitFetchHold()
1283
1284 self.wait(for: [joinWithBottleExpectation], timeout: 100)
1285
1286 var egoPeerID: String?
1287
1288 let dumpCallback = self.expectation(description: "dumpCallback callback occurs")
1289 self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) { dump, _ in
1290 XCTAssertNotNil(dump, "dump should not be nil")
1291 let egoSelf = dump!["self"] as? [String: AnyObject]
1292 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
1293 egoPeerID = egoSelf!["peerID"] as? String
1294 let dynamicInfo = egoSelf!["dynamicInfo"] as? [String: AnyObject]
1295 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
1296 let included = dynamicInfo!["included"] as? [String]
1297 XCTAssertNotNil(included, "included should not be nil")
1298 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
1299 dumpCallback.fulfill()
1300 }
1301 self.wait(for: [dumpCallback], timeout: 10)
1302
1303 self.verifyDatabaseMocks()
1304 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
1305 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
1306
1307 let bottles: [Bottle] = self.fakeCuttlefishServer.state.bottles
1308 var bottleToExclude: String?
1309 bottles.forEach { bottle in
1310 if bottle.peerID == egoPeerID {
1311 bottleToExclude = bottle.bottleID
1312 }
1313 }
1314
1315 XCTAssertNotNil(bottleToExclude, "bottleToExclude should not be nil")
1316
1317 //now call fetchviablebottles, we should get the uncached version
1318 var fetchUnCachedViableBottlesExpectation = self.expectation(description: "fetch UnCached ViableBottles")
1319
1320 self.fakeCuttlefishServer.fetchViableBottlesListener = { request in
1321 self.fakeCuttlefishServer.fetchViableBottlesListener = nil
1322 fetchUnCachedViableBottlesExpectation.fulfill()
1323 return nil
1324 }
1325 self.fakeCuttlefishServer.fetchViableBottlesDontReturnBottleWithID = bottleToExclude
1326 var FetchAllViableBottles = self.expectation(description: "FetchAllViableBottles callback occurs")
1327
1328 self.cuttlefishContext.rpcFetchAllViableBottles { viable, _, error in
1329 XCTAssertNil(error, "should be no error fetching viable bottles")
1330 XCTAssert(viable?.contains(bottle.bottleID) ?? false, "The bottle we're about to restore should be viable")
1331 FetchAllViableBottles.fulfill()
1332 }
1333 self.wait(for: [FetchAllViableBottles], timeout: 10)
1334 self.wait(for: [fetchUnCachedViableBottlesExpectation], timeout: 10)
1335
1336 //now call fetchviablebottles, we should get the uncached version
1337 fetchUnCachedViableBottlesExpectation = self.expectation(description: "fetch UnCached ViableBottles")
1338
1339 self.fakeCuttlefishServer.fetchViableBottlesListener = { request in
1340 self.fakeCuttlefishServer.fetchViableBottlesListener = nil
1341 fetchUnCachedViableBottlesExpectation.fulfill()
1342 return nil
1343 }
1344
1345 self.fakeCuttlefishServer.fetchViableBottlesDontReturnBottleWithID = bottleToExclude
1346
1347 FetchAllViableBottles = self.expectation(description: "FetchAllViableBottles callback occurs")
1348
1349 self.cuttlefishContext.rpcFetchAllViableBottles { viable, _, error in
1350 XCTAssertNil(error, "should be no error fetching viable bottles")
1351 XCTAssert(viable?.contains(bottle.bottleID) ?? false, "The bottle we're about to restore should be viable")
1352 FetchAllViableBottles.fulfill()
1353 }
1354 self.wait(for: [FetchAllViableBottles], timeout: 10)
1355 self.wait(for: [fetchUnCachedViableBottlesExpectation], timeout: 10)
1356 }
1357
1358 func testRecoverTLKSharesSendByPeers() throws {
1359 // First, set up the world: two peers, one of which has sent TLKs tto itself and the other
1360 let noSelfSharesContext = self.makeInitiatorContext(contextID: "noShares", authKitAdapter: self.mockAuthKit2)
1361 let allSharesContext = self.makeInitiatorContext(contextID: "allShares", authKitAdapter: self.mockAuthKit3)
1362
1363 self.startCKAccountStatusMock()
1364 let noSelfSharesPeerID = self.assertResetAndBecomeTrusted(context: noSelfSharesContext)
1365 let allSharesPeerID = self.assertJoinViaEscrowRecovery(joiningContext: allSharesContext, sponsor: noSelfSharesContext)
1366
1367 self.sendContainerChangeWaitForFetch(context: noSelfSharesContext)
1368 self.assertEnters(context: noSelfSharesContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
1369
1370 XCTAssertTrue(self.fakeCuttlefishServer.assertCuttlefishState(FakeCuttlefishAssertion(peer: noSelfSharesPeerID, opinion: .trusts, target: allSharesPeerID)),
1371 "noShares should trust allShares")
1372 XCTAssertTrue(self.fakeCuttlefishServer.assertCuttlefishState(FakeCuttlefishAssertion(peer: noSelfSharesPeerID, opinion: .trusts, target: noSelfSharesPeerID)),
1373 "No shares should trust itself")
1374
1375 self.putFakeKeyHierarchiesInCloudKit()
1376 try self.putSelfTLKSharesInCloudKit(context: allSharesContext)
1377 try self.putAllTLKSharesInCloudKit(to: noSelfSharesContext, from: allSharesContext)
1378
1379 try self.ckksZones.forEach { zone in
1380 XCTAssertFalse(try self.tlkShareInCloudKit(receiverPeerID: noSelfSharesPeerID, senderPeerID: noSelfSharesPeerID, zoneID: zone as! CKRecordZone.ID), "Should not have self shares for noSelfShares")
1381 XCTAssertTrue(try self.tlkShareInCloudKit(receiverPeerID: noSelfSharesPeerID, senderPeerID: allSharesPeerID, zoneID: zone as! CKRecordZone.ID), "Should have a share for noSelfShares from allShares")
1382 }
1383
1384 // Now, recover from noSelfShares
1385 self.assertAllCKKSViewsUpload(tlkShares: 1)
1386 self.assertJoinViaEscrowRecovery(joiningContext: self.cuttlefishContext, sponsor: noSelfSharesContext)
1387 // And CKKS should enter ready!
1388 self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
1389 self.verifyDatabaseMocks()
1390 }
1391 }
1392
1393 #endif