3 @objcMembers class OctagonEscrowRecoveryTests: OctagonTestsBase {
4 override func setUp() {
8 func testJoinWithBottle() throws {
9 let initiatorContextID = "initiator-context-id"
10 let bottlerContext = self.makeInitiatorContext(contextID: initiatorContextID)
12 bottlerContext.startOctagonStateMachine()
13 let ckacctinfo = CKAccountInfo()
14 ckacctinfo.accountStatus = .available
15 ckacctinfo.hasValidCredentials = true
16 ckacctinfo.accountPartition = .production
18 bottlerContext.cloudkitAccountStateChange(nil, to: ckacctinfo)
19 self.assertEnters(context: bottlerContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
22 let bottlerotcliqueContext = OTConfigurationContext()
23 bottlerotcliqueContext.context = initiatorContextID
24 bottlerotcliqueContext.dsid = "1234"
25 bottlerotcliqueContext.altDSID = self.mockAuthKit.altDSID!
26 bottlerotcliqueContext.otControl = self.otControl
28 clique = try OTClique.newFriends(withContextData: bottlerotcliqueContext, resetReason: .testGenerated)
29 XCTAssertNotNil(clique, "Clique should not be nil")
30 XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call")
32 XCTFail("Shouldn't have errored making new friends: \(error)")
36 self.assertEnters(context: bottlerContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
37 self.assertConsidersSelfTrusted(context: bottlerContext)
39 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
40 XCTAssertNotNil(entropy, "entropy should not be nil")
42 // Fake that this peer also created some TLKShares for itself
43 self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID)
44 try self.putSelfTLKShareInCloudKit(context: bottlerContext, zoneID: self.manateeZoneID)
46 let bottle = self.fakeCuttlefishServer.state.bottles[0]
48 self.cuttlefishContext.startOctagonStateMachine()
49 self.startCKAccountStatusMock()
50 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
52 // Try to enforce that that CKKS doesn't know about the key hierarchy until Octagon asks it
53 self.holdCloudKitFetches()
55 // Note: CKKS will want to upload a TLKShare for its self
56 self.expectCKModifyKeyRecords(0, currentKeyPointerRecords: 0, tlkShareRecords: 1, zoneID: self.manateeZoneID)
58 // Before you call joinWithBottle, you need to call fetchViableBottles.
59 let fetchViableExpectation = self.expectation(description: "fetchViableBottles callback occurs")
60 self.cuttlefishContext.rpcFetchAllViableBottles { viable, _, error in
61 XCTAssertNil(error, "should be no error fetching viable bottles")
62 XCTAssert(viable?.contains(bottle.bottleID) ?? false, "The bottle we're about to restore should be viable")
63 fetchViableExpectation.fulfill()
65 self.wait(for: [fetchViableExpectation], timeout: 10)
67 let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs")
68 self.cuttlefishContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID) { error in
69 XCTAssertNil(error, "error should be nil")
70 joinWithBottleExpectation.fulfill()
74 self.releaseCloudKitFetchHold()
76 self.wait(for: [joinWithBottleExpectation], timeout: 100)
78 let dumpCallback = self.expectation(description: "dumpCallback callback occurs")
79 self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) {
81 XCTAssertNotNil(dump, "dump should not be nil")
82 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
83 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
84 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
85 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
86 let included = dynamicInfo!["included"] as? Array<String>
87 XCTAssertNotNil(included, "included should not be nil")
88 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
89 dumpCallback.fulfill()
91 self.wait(for: [dumpCallback], timeout: 10)
93 self.verifyDatabaseMocks()
94 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
95 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
98 func testBottleRestoreEntersOctagonReady() throws {
99 self.startCKAccountStatusMock()
101 let initiatorContextID = "initiator-context-id"
102 self.cuttlefishContext.startOctagonStateMachine()
103 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
107 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
108 XCTAssertNotNil(clique, "Clique should not be nil")
110 XCTFail("Shouldn't have errored making new friends: \(error)")
114 self.verifyDatabaseMocks()
116 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
117 XCTAssertNotNil(entropy, "entropy should not be nil")
119 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
120 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
122 let bottle = self.fakeCuttlefishServer.state.bottles[0]
124 let initiatorContext = self.makeInitiatorContext(contextID: initiatorContextID)
126 initiatorContext.startOctagonStateMachine()
127 let restoreExpectation = self.expectation(description: "restore returns")
129 self.manager!.restore(OTCKContainerName, contextID: initiatorContextID, bottleSalt: self.otcliqueContext.altDSID, entropy: entropy!, bottleID: bottle.bottleID) { error in
130 XCTAssertNil(error, "error should be nil")
131 restoreExpectation.fulfill()
133 self.wait(for: [restoreExpectation], timeout: 10)
135 self.assertEnters(context: initiatorContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
137 let initiatorDumpCallback = self.expectation(description: "initiatorDumpCallback callback occurs")
138 self.tphClient.dump(withContainer: OTCKContainerName, context: initiatorContextID) {
140 XCTAssertNotNil(dump, "dump should not be nil")
141 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
142 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
143 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
144 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
145 let included = dynamicInfo!["included"] as? Array<String>
146 XCTAssertNotNil(included, "included should not be nil")
147 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
149 initiatorDumpCallback.fulfill()
151 self.wait(for: [initiatorDumpCallback], timeout: 10)
154 func testJoinWithBottleWithCKKSConflict() throws {
155 let initiatorContextID = "initiator-context-id"
156 let bottlerContext = self.makeInitiatorContext(contextID: initiatorContextID)
158 bottlerContext.startOctagonStateMachine()
159 let ckacctinfo = CKAccountInfo()
160 ckacctinfo.accountStatus = .available
161 ckacctinfo.hasValidCredentials = true
162 ckacctinfo.accountPartition = .production
164 bottlerContext.cloudkitAccountStateChange(nil, to: ckacctinfo)
165 self.assertEnters(context: bottlerContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
168 let bottlerotcliqueContext = OTConfigurationContext()
169 bottlerotcliqueContext.context = initiatorContextID
170 bottlerotcliqueContext.dsid = "1234"
171 bottlerotcliqueContext.altDSID = self.mockAuthKit.altDSID!
172 bottlerotcliqueContext.otControl = self.otControl
174 clique = try OTClique.newFriends(withContextData: bottlerotcliqueContext, resetReason: .testGenerated)
175 XCTAssertNotNil(clique, "Clique should not be nil")
176 XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call")
178 XCTFail("Shouldn't have errored making new friends: \(error)")
182 self.assertEnters(context: bottlerContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
183 self.assertConsidersSelfTrusted(context: bottlerContext)
185 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
186 XCTAssertNotNil(entropy, "entropy should not be nil")
188 let bottle = self.fakeCuttlefishServer.state.bottles[0]
190 // During the join, there's a CKKS key race
191 self.silentFetchesAllowed = false
192 self.expectCKFetchAndRun(beforeFinished: {
193 self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID)
194 self.putFakeDeviceStatus(inCloudKit: self.manateeZoneID)
195 self.silentFetchesAllowed = true
198 self.cuttlefishContext.startOctagonStateMachine()
199 self.startCKAccountStatusMock()
200 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
202 // Before you call joinWithBottle, you need to call fetchViableBottles.
203 let fetchViableExpectation = self.expectation(description: "fetchViableBottles callback occurs")
204 self.cuttlefishContext.rpcFetchAllViableBottles { viable, _, error in
205 XCTAssertNil(error, "should be no error fetching viable bottles")
206 XCTAssert(viable?.contains(bottle.bottleID) ?? false, "The bottle we're about to restore should be viable")
207 fetchViableExpectation.fulfill()
209 self.wait(for: [fetchViableExpectation], timeout: 10)
211 let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs")
212 self.cuttlefishContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID) { error in
213 XCTAssertNil(error, "error should be nil")
214 joinWithBottleExpectation.fulfill()
217 self.wait(for: [joinWithBottleExpectation], timeout: 100)
219 self.verifyDatabaseMocks()
220 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
221 assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLK, within: 10 * NSEC_PER_SEC)
224 func testBottleRestoreWithSameMachineID() throws {
225 self.startCKAccountStatusMock()
227 self.cuttlefishContext.startOctagonStateMachine()
228 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
232 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
233 XCTAssertNotNil(clique, "Clique should not be nil")
235 XCTFail("Shouldn't have errored making new friends: \(error)")
239 self.verifyDatabaseMocks()
241 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
242 XCTAssertNotNil(entropy, "entropy should not be nil")
244 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
245 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
247 let bottle = self.fakeCuttlefishServer.state.bottles[0]
249 // some other peer should restore from this bottle
250 let differentDevice = self.makeInitiatorContext(contextID: "differenDevice")
251 let differentRestoreExpectation = self.expectation(description: "different restore returns")
252 differentDevice.startOctagonStateMachine()
253 differentDevice.join(withBottle: bottle.bottleID,
255 bottleSalt: self.otcliqueContext.altDSID) { error in
256 XCTAssertNil(error, "error should be nil")
257 differentRestoreExpectation.fulfill()
259 self.wait(for: [differentRestoreExpectation], timeout: 10)
261 self.assertTrusts(context: differentDevice, includedPeerIDCount: 2, excludedPeerIDCount: 0)
263 // The first peer will upload TLKs for the new peer
264 self.assertAllCKKSViewsUpload(tlkShares: 1)
265 self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext)
266 self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
267 self.verifyDatabaseMocks()
269 self.assertTrusts(context: self.cuttlefishContext, includedPeerIDCount: 2, excludedPeerIDCount: 0)
271 // Explicitly use the same authkit parameters as the original peer when restoring this time
272 let restoreContext = self.makeInitiatorContext(contextID: "restoreContext", authKitAdapter: self.mockAuthKit)
274 restoreContext.startOctagonStateMachine()
276 let restoreExpectation = self.expectation(description: "restore returns")
277 restoreContext.join(withBottle: bottle.bottleID,
279 bottleSalt: self.otcliqueContext.altDSID) { error in
280 XCTAssertNil(error, "error should be nil")
281 restoreExpectation.fulfill()
283 self.wait(for: [restoreExpectation], timeout: 10)
285 self.assertEnters(context: restoreContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
286 self.assertConsidersSelfTrusted(context: restoreContext)
288 // The restore context should exclude its sponsor
289 self.assertTrusts(context: restoreContext, includedPeerIDCount: 2, excludedPeerIDCount: 1)
291 // Then, the remote peer should trust the new peer and exclude the original
292 self.sendContainerChangeWaitForFetchForStates(context: differentDevice, states: [OctagonStateReadyUpdated, OctagonStateReady])
293 self.assertTrusts(context: differentDevice, includedPeerIDCount: 2, excludedPeerIDCount: 1)
295 // Then, if by some strange miracle the original peer is still around, it should bail (as it's now untrusted)
296 self.sendContainerChangeWaitForFetchForStates(context: self.cuttlefishContext, states: [OctagonStateUntrusted])
297 self.assertConsidersSelfUntrusted(context: self.cuttlefishContext)
298 self.assertTrusts(context: self.cuttlefishContext, includedPeerIDCount: 0, excludedPeerIDCount: 1)
301 func testRestoreSPIFromPiggybackingState() throws {
302 self.startCKAccountStatusMock()
304 let initiatorContextID = "initiator-context-id"
305 self.cuttlefishContext.startOctagonStateMachine()
306 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
310 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
311 XCTAssertNotNil(clique, "Clique should not be nil")
313 XCTFail("Shouldn't have errored making new friends: \(error)")
316 self.verifyDatabaseMocks()
318 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
319 XCTAssertNotNil(entropy, "entropy should not be nil")
321 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
322 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
324 let bottle = self.fakeCuttlefishServer.state.bottles[0]
326 let initiatorContext = self.makeInitiatorContext(contextID: initiatorContextID)
328 initiatorContext.startOctagonStateMachine()
330 self.assertEnters(context: initiatorContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
332 let restoreExpectation = self.expectation(description: "restore returns")
334 self.manager!.restore(OTCKContainerName, contextID: initiatorContextID, bottleSalt: self.otcliqueContext.altDSID, entropy: entropy!, bottleID: bottle.bottleID) { error in
335 XCTAssertNil(error, "error should be nil")
336 restoreExpectation.fulfill()
338 self.wait(for: [restoreExpectation], timeout: 10)
340 let initiatorDumpCallback = self.expectation(description: "initiatorDumpCallback callback occurs")
341 self.tphClient.dump(withContainer: OTCKContainerName, context: initiatorContextID) {
343 XCTAssertNotNil(dump, "dump should not be nil")
344 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
345 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
346 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
347 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
348 let included = dynamicInfo!["included"] as? Array<String>
349 XCTAssertNotNil(included, "included should not be nil")
350 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
352 initiatorDumpCallback.fulfill()
354 self.wait(for: [initiatorDumpCallback], timeout: 10)
357 func testRestoreBadBottleIDFails() throws {
358 self.startCKAccountStatusMock()
360 let initiatorContextID = "initiator-context-id"
361 self.cuttlefishContext.startOctagonStateMachine()
362 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
366 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
367 XCTAssertNotNil(clique, "Clique should not be nil")
369 XCTFail("Shouldn't have errored making new friends: \(error)")
373 self.verifyDatabaseMocks()
375 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
376 XCTAssertNotNil(entropy, "entropy should not be nil")
378 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
379 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
381 _ = self.fakeCuttlefishServer.state.bottles[0]
383 let initiatorContext = self.makeInitiatorContext(contextID: initiatorContextID)
385 initiatorContext.startOctagonStateMachine()
387 let restoreExpectation = self.expectation(description: "restore returns")
389 self.manager!.restore(OTCKContainerName, contextID: initiatorContextID, bottleSalt: self.otcliqueContext.altDSID, entropy: entropy!, bottleID: "bad escrow record ID") { error in
390 XCTAssertNotNil(error, "error should not be nil")
391 restoreExpectation.fulfill()
393 self.wait(for: [restoreExpectation], timeout: 10)
394 self.assertEnters(context: initiatorContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
396 let acceptorDumpCallback = self.expectation(description: "acceptorDumpCallback callback occurs")
397 self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) {
399 XCTAssertNotNil(dump, "dump should not be nil")
400 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
401 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
402 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
403 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
404 let included = dynamicInfo!["included"] as? Array<String>
405 XCTAssertNotNil(included, "included should not be nil")
406 XCTAssertEqual(included!.count, 1, "should be 1 peer ids")
408 acceptorDumpCallback.fulfill()
410 self.wait(for: [acceptorDumpCallback], timeout: 10)
413 func testRestoreOptimalBottleIDs() throws {
414 self.startCKAccountStatusMock()
418 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
419 XCTAssertNotNil(clique, "Clique should not be nil")
421 XCTFail("Shouldn't have errored making new friends: \(error)")
425 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
426 XCTAssertNotNil(entropy, "entropy should not be nil")
428 var bottleIDs = try OTClique.findOptimalBottleIDs(withContextData: self.otcliqueContext)
429 XCTAssertNotNil(bottleIDs.preferredBottleIDs, "preferredBottleIDs should not be nil")
430 XCTAssertEqual(bottleIDs.preferredBottleIDs.count, 1, "preferredBottleIDs should have 1 bottle")
431 XCTAssertEqual(bottleIDs.partialRecoveryBottleIDs.count, 0, "partialRecoveryBottleIDs should be empty")
433 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
434 self.verifyDatabaseMocks()
435 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
437 let bottle = self.fakeCuttlefishServer.state.bottles[0]
439 let initiatorContext = self.manager.context(forContainerName: OTCKContainerName,
440 contextID: "restoreContext",
441 sosAdapter: OTSOSMissingAdapter(),
442 authKitAdapter: self.mockAuthKit2,
443 lockStateTracker: self.lockStateTracker,
444 accountStateTracker: self.accountStateTracker,
445 deviceInformationAdapter: self.makeInitiatorDeviceInfoAdapter())
447 initiatorContext.startOctagonStateMachine()
448 let newOTCliqueContext = OTConfigurationContext()
449 newOTCliqueContext.context = "restoreContext"
450 newOTCliqueContext.dsid = self.otcliqueContext.dsid
451 newOTCliqueContext.altDSID = self.otcliqueContext.altDSID
452 newOTCliqueContext.otControl = self.otcliqueContext.otControl
453 newOTCliqueContext.sbd = OTMockSecureBackup(bottleID: bottle.bottleID, entropy: entropy!)
455 let newClique: OTClique
457 newClique = try OTClique.performEscrowRecovery(withContextData: newOTCliqueContext, escrowArguments: [:])
458 XCTAssertNotNil(newClique, "newClique should not be nil")
460 XCTFail("Shouldn't have errored recovering: \(error)")
464 // We will upload a new TLK for the new peer
465 self.assertAllCKKSViewsUpload(tlkShares: 1)
466 self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext)
467 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
469 self.sendContainerChangeWaitForFetch(context: initiatorContext)
470 bottleIDs = try OTClique.findOptimalBottleIDs(withContextData: self.otcliqueContext)
471 XCTAssertNotNil(bottleIDs.preferredBottleIDs, "preferredBottleIDs should not be nil")
472 XCTAssertEqual(bottleIDs.preferredBottleIDs.count, 2, "preferredBottleIDs should have 2 bottle")
473 XCTAssertEqual(bottleIDs.partialRecoveryBottleIDs.count, 0, "partialRecoveryBottleIDs should be empty")
476 func testRestoreFromEscrowContents() throws {
477 self.startCKAccountStatusMock()
481 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
482 XCTAssertNotNil(clique, "Clique should not be nil")
484 XCTFail("Shouldn't have errored making new friends: \(error)")
488 var bottleIDs = try OTClique.findOptimalBottleIDs(withContextData: self.otcliqueContext)
489 XCTAssertNotNil(bottleIDs.preferredBottleIDs, "preferredBottleIDs should not be nil")
490 XCTAssertEqual(bottleIDs.preferredBottleIDs.count, 1, "preferredBottleIDs should have 1 bottle")
491 XCTAssertEqual(bottleIDs.partialRecoveryBottleIDs.count, 0, "partialRecoveryBottleIDs should be empty")
493 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
494 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
495 self.verifyDatabaseMocks()
498 var bottledID: String = ""
500 let fetchEscrowContentsExpectation = self.expectation(description: "fetchEscrowContentsExpectation returns")
501 clique.fetchEscrowContents { e, b, s, _ in
502 XCTAssertNotNil(e, "entropy should not be nil")
503 XCTAssertNotNil(b, "bottleID should not be nil")
504 XCTAssertNotNil(s, "signingPublicKey should not be nil")
507 fetchEscrowContentsExpectation.fulfill()
510 self.wait(for: [fetchEscrowContentsExpectation], timeout: 10)
512 let initiatorContext = self.manager.context(forContainerName: OTCKContainerName,
513 contextID: "restoreContext",
514 sosAdapter: OTSOSMissingAdapter(),
515 authKitAdapter: self.mockAuthKit2,
516 lockStateTracker: self.lockStateTracker,
517 accountStateTracker: self.accountStateTracker,
518 deviceInformationAdapter: self.makeInitiatorDeviceInfoAdapter())
520 initiatorContext.startOctagonStateMachine()
521 let newOTCliqueContext = OTConfigurationContext()
522 newOTCliqueContext.context = "restoreContext"
523 newOTCliqueContext.dsid = self.otcliqueContext.dsid
524 newOTCliqueContext.altDSID = self.otcliqueContext.altDSID
525 newOTCliqueContext.otControl = self.otcliqueContext.otControl
526 newOTCliqueContext.sbd = OTMockSecureBackup(bottleID: bottledID, entropy: entropy)
528 let newClique: OTClique
530 newClique = try OTClique.performEscrowRecovery(withContextData: newOTCliqueContext, escrowArguments: [:])
531 XCTAssertNotNil(newClique, "newClique should not be nil")
533 XCTFail("Shouldn't have errored recovering: \(error)")
537 // We will upload a new TLK for the new peer
538 self.assertAllCKKSViewsUpload(tlkShares: 1)
539 self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext)
540 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
542 self.sendContainerChangeWaitForFetch(context: initiatorContext)
544 bottleIDs = try OTClique.findOptimalBottleIDs(withContextData: self.otcliqueContext)
545 XCTAssertNotNil(bottleIDs.preferredBottleIDs, "preferredBottleIDs should not be nil")
546 XCTAssertEqual(bottleIDs.preferredBottleIDs.count, 2, "preferredBottleIDs should have 2 bottle")
547 XCTAssertEqual(bottleIDs.partialRecoveryBottleIDs.count, 0, "partialRecoveryBottleIDs should be empty")
549 let dumpExpectation = self.expectation(description: "dump callback occurs")
550 self.tphClient.dump(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) {
552 XCTAssertNil(error, "Should be no error dumping data")
553 XCTAssertNotNil(dump, "dump should not be nil")
554 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
555 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
556 let peerID = egoSelf!["peerID"] as? String
557 XCTAssertNotNil(peerID, "peerID should not be nil")
559 dumpExpectation.fulfill()
561 self.wait(for: [dumpExpectation], timeout: 10)
563 self.otControlCLI.status(OTCKContainerName,
564 context: newOTCliqueContext.context!,
568 func testFetchEmptyOptimalBottleList () throws {
569 self.startCKAccountStatusMock()
571 let bottleIDs = try OTClique.findOptimalBottleIDs(withContextData: self.otcliqueContext)
572 XCTAssertEqual(bottleIDs.preferredBottleIDs.count, 0, "should be 0 preferred bottle ids")
573 XCTAssertEqual(bottleIDs.partialRecoveryBottleIDs.count, 0, "should be 0 partialRecoveryBottleIDs")
576 func testFetchOptimalBottlesAfterFailedRestore() throws {
577 self.startCKAccountStatusMock()
579 self.otcliqueContext.sbd = OTMockSecureBackup(bottleID: "bottle ID", entropy: Data(count: 72))
581 XCTAssertThrowsError(try OTClique.performEscrowRecovery(withContextData: self.otcliqueContext, escrowArguments: [:]))
583 let bottleIDs = try OTClique.findOptimalBottleIDs(withContextData: self.otcliqueContext)
584 XCTAssertNotNil(bottleIDs.preferredBottleIDs, "preferredBottleIDs should not be nil")
585 XCTAssertEqual(bottleIDs.preferredBottleIDs.count, 0, "preferredBottleIDs should have 0 bottle")
586 XCTAssertEqual(bottleIDs.partialRecoveryBottleIDs.count, 0, "partialRecoveryBottleIDs should be empty")
589 func testMakeNewFriendsAndFetchEscrowContents () throws {
590 self.startCKAccountStatusMock()
594 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
595 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
596 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
598 let fetchEscrowContentsException = self.expectation(description: "update returns")
600 clique.fetchEscrowContents { e, b, s, _ in
601 XCTAssertNotNil(e, "entropy should not be nil")
602 XCTAssertNotNil(b, "bottleID should not be nil")
603 XCTAssertNotNil(s, "signingPublicKey should not be nil")
604 fetchEscrowContentsException.fulfill()
606 self.wait(for: [fetchEscrowContentsException], timeout: 10)
609 XCTFail("failed to reset clique: \(error)")
612 self.verifyDatabaseMocks()
615 func testFetchEscrowContentsBeforeIdentityExists() {
616 self.startCKAccountStatusMock()
618 let initiatorContext = self.manager.context(forContainerName: OTCKContainerName, contextID: "initiator")
619 let fetchEscrowContentsException = self.expectation(description: "update returns")
621 initiatorContext.fetchEscrowContents { entropy, bottleID, signingPubKey, error in
622 XCTAssertNil(entropy, "entropy should be nil")
623 XCTAssertNil(bottleID, "bottleID should be nil")
624 XCTAssertNil(signingPubKey, "signingPublicKey should be nil")
625 XCTAssertNotNil(error, "error should not be nil")
627 fetchEscrowContentsException.fulfill()
629 self.wait(for: [fetchEscrowContentsException], timeout: 10)
632 func testFetchEscrowContentsChecksEntitlement() throws {
633 self.startCKAccountStatusMock()
635 let contextName = OTDefaultContext
636 let containerName = OTCKContainerName
638 // First, fail due to not having any data
639 let fetchEscrowContentsExpectation = self.expectation(description: "fetchEscrowContentsExpectation returns")
640 self.otControl.fetchEscrowContents(containerName, contextID: contextName) { entropy, bottle, signingPublicKey, error in
641 XCTAssertNotNil(error, "error should not be nil")
642 //XCTAssertNotEqual(error.code, errSecMissingEntitlement, "Error should not be 'missing entitlement'")
643 XCTAssertNil(entropy, "entropy should be nil")
644 XCTAssertNil(bottle, "bottleID should be nil")
645 XCTAssertNil(signingPublicKey, "signingPublicKey should be nil")
646 fetchEscrowContentsExpectation.fulfill()
648 self.wait(for: [fetchEscrowContentsExpectation], timeout: 10)
650 // Now, fail due to the client not having an entitlement
651 self.otControlEntitlementBearer.entitlements.removeAll()
652 let failFetchEscrowContentsExpectation = self.expectation(description: "fetchEscrowContentsExpectation returns")
653 self.otControl.fetchEscrowContents(containerName, contextID: contextName) { entropy, bottle, signingPublicKey, error in
654 XCTAssertNotNil(error, "error should not be nil")
656 case .some(let error as NSError):
657 XCTAssertEqual(error.domain, NSOSStatusErrorDomain, "Error should be an OS status")
658 XCTAssertEqual(error.code, Int(errSecMissingEntitlement), "Error should be 'missing entitlement'")
660 XCTFail("Unable to turn error into NSError: \(String(describing: error))")
663 XCTAssertNil(entropy, "entropy should not be nil")
664 XCTAssertNil(bottle, "bottleID should not be nil")
665 XCTAssertNil(signingPublicKey, "signingPublicKey should not be nil")
666 failFetchEscrowContentsExpectation.fulfill()
668 self.wait(for: [failFetchEscrowContentsExpectation], timeout: 10)
671 func testJoinWithBottleFailCaseBottleDoesntExist() throws {
672 self.startCKAccountStatusMock()
674 let initiatorContextID = "initiator-context-id"
675 self.cuttlefishContext.startOctagonStateMachine()
676 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
680 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
681 XCTAssertNotNil(clique, "Clique should not be nil")
683 XCTFail("Shouldn't have errored making new friends: \(error)")
687 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
688 XCTAssertNotNil(entropy, "entropy should not be nil")
690 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
691 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
692 self.verifyDatabaseMocks()
694 let bottle = self.fakeCuttlefishServer.state.bottles[0]
695 self.fakeCuttlefishServer.state.bottles.removeAll()
697 let initiatorContext = self.makeInitiatorContext(contextID: initiatorContextID)
699 initiatorContext.startOctagonStateMachine()
701 let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs")
702 initiatorContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID) { error in
703 XCTAssertNotNil(error, "error should not be nil")
704 joinWithBottleExpectation.fulfill()
706 self.wait(for: [joinWithBottleExpectation], timeout: 10)
707 self.assertEnters(context: initiatorContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
710 func testJoinWithBottleFailCaseBadEscrowRecord() throws {
711 self.startCKAccountStatusMock()
713 let initiatorContextID = "initiator-context-id"
715 self.cuttlefishContext.startOctagonStateMachine()
716 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
720 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
721 XCTAssertNotNil(clique, "Clique should not be nil")
723 XCTFail("Shouldn't have errored making new friends: \(error)")
727 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
728 XCTAssertNotNil(entropy, "entropy should not be nil")
730 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
731 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
732 self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
733 self.verifyDatabaseMocks()
735 _ = self.fakeCuttlefishServer.state.bottles[0]
737 let initiatorContext = self.makeInitiatorContext(contextID: initiatorContextID)
739 initiatorContext.startOctagonStateMachine()
741 let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs")
742 initiatorContext.join(withBottle: "sos peer id", entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID) { error in
743 XCTAssertNotNil(error, "error should be nil")
744 joinWithBottleExpectation.fulfill()
746 self.wait(for: [joinWithBottleExpectation], timeout: 10)
747 self.assertEnters(context: initiatorContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
750 func testJoinWithBottleFailCaseBadEntropy() throws {
751 self.startCKAccountStatusMock()
753 let initiatorContextID = "initiator-context-id"
754 self.cuttlefishContext.startOctagonStateMachine()
755 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
759 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
760 XCTAssertNotNil(clique, "Clique should not be nil")
762 XCTFail("Shouldn't have errored making new friends: \(error)")
766 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
767 XCTAssertNotNil(entropy, "entropy should not be nil")
769 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
770 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
771 self.verifyDatabaseMocks()
773 let bottle = self.fakeCuttlefishServer.state.bottles[0]
775 let initiatorContext = self.makeInitiatorContext(contextID: initiatorContextID)
777 initiatorContext.startOctagonStateMachine()
779 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
781 let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs")
782 initiatorContext.join(withBottle: bottle.bottleID, entropy: Data(count: 72), bottleSalt: self.otcliqueContext.altDSID) { error in
783 XCTAssertNotNil(error, "error should not be nil, when entropy is missing")
784 joinWithBottleExpectation.fulfill()
786 self.wait(for: [joinWithBottleExpectation], timeout: 10)
787 self.assertEnters(context: initiatorContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
790 func testJoinWithBottleFailCaseBadBottleSalt() throws {
791 self.startCKAccountStatusMock()
793 let initiatorContextID = "initiator-context-id"
794 self.cuttlefishContext.startOctagonStateMachine()
795 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
799 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
800 XCTAssertNotNil(clique, "Clique should not be nil")
802 XCTFail("Shouldn't have errored making new friends: \(error)")
806 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
807 XCTAssertNotNil(entropy, "entropy should not be nil")
809 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
810 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
811 self.verifyDatabaseMocks()
813 let bottle = self.fakeCuttlefishServer.state.bottles[0]
815 let initiatorContext = self.makeInitiatorContext(contextID: initiatorContextID)
817 initiatorContext.startOctagonStateMachine()
819 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
821 let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs")
822 initiatorContext.join(withBottle: bottle.bottleID, entropy: Data(count: 72), bottleSalt: "123456789") { error in
823 XCTAssertNotNil(error, "error should not be nil with bad entropy and salt")
824 joinWithBottleExpectation.fulfill()
826 self.wait(for: [joinWithBottleExpectation], timeout: 10)
827 self.assertEnters(context: initiatorContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
830 func testRecoverFromDeviceNotOnMachineIDList() throws {
831 self.startCKAccountStatusMock()
835 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
836 XCTAssertNotNil(clique, "Clique should not be nil")
838 XCTFail("Shouldn't have errored making new friends: \(error)")
842 let firstPeerID = clique.cliqueMemberIdentifier
843 XCTAssertNotNil(firstPeerID, "Clique should have a member identifier")
844 let entropy = try self.loadSecret(label: firstPeerID!)
845 XCTAssertNotNil(entropy, "entropy should not be nil")
847 let bottleIDs = try OTClique.findOptimalBottleIDs(withContextData: self.otcliqueContext)
848 XCTAssertNotNil(bottleIDs.preferredBottleIDs, "preferredBottleIDs should not be nil")
849 XCTAssertEqual(bottleIDs.preferredBottleIDs.count, 1, "preferredBottleIDs should have 1 bottle")
850 XCTAssertEqual(bottleIDs.partialRecoveryBottleIDs.count, 0, "partialRecoveryBottleIDs should be empty")
852 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
853 self.verifyDatabaseMocks()
854 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
856 let bottle = self.fakeCuttlefishServer.state.bottles[0]
858 // To get into the state we need, we need to introduce peer B and C. C should then distrust A, whose bottle it used
859 // B shouldn't have an opinion of C.
861 let bNewOTCliqueContext = OTConfigurationContext()
862 bNewOTCliqueContext.context = "restoreB"
863 bNewOTCliqueContext.dsid = self.otcliqueContext.dsid
864 bNewOTCliqueContext.altDSID = self.otcliqueContext.altDSID
865 bNewOTCliqueContext.otControl = self.otcliqueContext.otControl
866 bNewOTCliqueContext.sbd = OTMockSecureBackup(bottleID: bottle.bottleID, entropy: entropy!)
868 let deviceBmockAuthKit = OTMockAuthKitAdapter(altDSID: self.otcliqueContext.altDSID,
869 machineID: "b-machine-id",
870 otherDevices: [self.mockAuthKit.currentMachineID])
872 let bRestoreContext = self.manager.context(forContainerName: OTCKContainerName,
873 contextID: bNewOTCliqueContext.context!,
874 sosAdapter: OTSOSMissingAdapter(),
875 authKitAdapter: deviceBmockAuthKit,
876 lockStateTracker: self.lockStateTracker,
877 accountStateTracker: self.accountStateTracker,
878 deviceInformationAdapter: self.makeInitiatorDeviceInfoAdapter())
879 bRestoreContext.startOctagonStateMachine()
880 let bNewClique: OTClique
882 bNewClique = try OTClique.performEscrowRecovery(withContextData: bNewOTCliqueContext, escrowArguments: [:])
883 XCTAssertNotNil(bNewClique, "bNewClique should not be nil")
885 XCTFail("Shouldn't have errored recovering: \(error)")
888 self.assertEnters(context: bRestoreContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
890 // And introduce C, which will kick out A
891 // During the next sign in, the machine ID list has changed to just the new one
892 let restoremockAuthKit = OTMockAuthKitAdapter(altDSID: self.otcliqueContext.altDSID,
893 machineID: "c-machine-id",
894 otherDevices: [self.mockAuthKit.currentMachineID, deviceBmockAuthKit.currentMachineID])
895 let restoreContext = self.manager.context(forContainerName: OTCKContainerName,
896 contextID: "restoreContext",
897 sosAdapter: OTSOSMissingAdapter(),
898 authKitAdapter: restoremockAuthKit,
899 lockStateTracker: self.lockStateTracker,
900 accountStateTracker: self.accountStateTracker,
901 deviceInformationAdapter: self.makeInitiatorDeviceInfoAdapter())
903 restoreContext.startOctagonStateMachine()
904 let newOTCliqueContext = OTConfigurationContext()
905 newOTCliqueContext.context = "restoreContext"
906 newOTCliqueContext.dsid = self.otcliqueContext.dsid
907 newOTCliqueContext.altDSID = self.otcliqueContext.altDSID
908 newOTCliqueContext.otControl = self.otcliqueContext.otControl
909 newOTCliqueContext.sbd = OTMockSecureBackup(bottleID: bottle.bottleID, entropy: entropy!)
911 let newClique: OTClique
913 newClique = try OTClique.performEscrowRecovery(withContextData: newOTCliqueContext, escrowArguments: [:])
914 XCTAssertNotNil(newClique, "newClique should not be nil")
916 XCTFail("Shouldn't have errored recovering: \(error)")
920 self.assertEnters(context: restoreContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
921 self.verifyDatabaseMocks()
922 self.assertConsidersSelfTrusted(context: restoreContext)
924 let restoreDumpCallback = self.expectation(description: "acceptorDumpCallback callback occurs")
925 self.tphClient.dump(withContainer: OTCKContainerName, context: newOTCliqueContext.context!) {
927 XCTAssertNotNil(dump, "dump should not be nil")
928 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
929 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
930 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
931 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
932 let included = dynamicInfo!["included"] as? Array<String>
933 XCTAssertNotNil(included, "included should not be nil")
934 XCTAssertEqual(included!.count, 3, "should be 3 peer id included")
936 restoreDumpCallback.fulfill()
938 self.wait(for: [restoreDumpCallback], timeout: 10)
940 // Now, exclude peer A's machine ID
941 restoremockAuthKit.otherDevices = [deviceBmockAuthKit.currentMachineID]
943 // Peer C should upload a new trust status
944 let updateTrustExpectation = self.expectation(description: "updateTrust")
945 self.fakeCuttlefishServer.updateListener = { request in
946 XCTAssertTrue(request.hasDynamicInfoAndSig, "updateTrust request should have a dynamic info")
947 let newDynamicInfo = TPPeerDynamicInfo(data: request.dynamicInfoAndSig.peerDynamicInfo,
948 sig: request.dynamicInfoAndSig.sig)
949 XCTAssertNotNil(newDynamicInfo, "should be able to make a dynamic info from protobuf")
951 XCTAssertEqual(newDynamicInfo!.excludedPeerIDs.count, 1, "Should have a single excluded peer")
952 updateTrustExpectation.fulfill()
956 restoreContext.incompleteNotificationOfMachineIDListChange()
957 self.wait(for: [updateTrustExpectation], timeout: 10)
959 self.assertEnters(context: restoreContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
960 self.verifyDatabaseMocks()
961 self.assertConsidersSelfTrusted(context: restoreContext)
964 func testCachedBottleFetch() throws {
965 let initiatorContextID = "initiator-context-id"
966 let bottlerContext = self.makeInitiatorContext(contextID: initiatorContextID)
968 bottlerContext.startOctagonStateMachine()
969 let ckacctinfo = CKAccountInfo()
970 ckacctinfo.accountStatus = .available
971 ckacctinfo.hasValidCredentials = true
972 ckacctinfo.accountPartition = .production
974 bottlerContext.cloudkitAccountStateChange(nil, to: ckacctinfo)
975 self.assertEnters(context: bottlerContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
978 let bottlerotcliqueContext = OTConfigurationContext()
979 bottlerotcliqueContext.context = initiatorContextID
980 bottlerotcliqueContext.dsid = "1234"
981 bottlerotcliqueContext.altDSID = self.mockAuthKit.altDSID!
982 bottlerotcliqueContext.otControl = self.otControl
984 clique = try OTClique.newFriends(withContextData: bottlerotcliqueContext, resetReason: .testGenerated)
985 XCTAssertNotNil(clique, "Clique should not be nil")
986 XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call")
988 XCTFail("Shouldn't have errored making new friends: \(error)")
992 self.assertEnters(context: bottlerContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
993 self.assertConsidersSelfTrusted(context: bottlerContext)
995 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
996 XCTAssertNotNil(entropy, "entropy should not be nil")
998 // Fake that this peer also created some TLKShares for itself
999 self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID)
1000 try self.putSelfTLKShareInCloudKit(context: bottlerContext, zoneID: self.manateeZoneID)
1002 let bottle = self.fakeCuttlefishServer.state.bottles[0]
1004 self.cuttlefishContext.startOctagonStateMachine()
1005 self.startCKAccountStatusMock()
1006 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
1008 // Try to enforce that that CKKS doesn't know about the key hierarchy until Octagon asks it
1009 self.holdCloudKitFetches()
1011 // Note: CKKS will want to upload a TLKShare for its self
1012 self.expectCKModifyKeyRecords(0, currentKeyPointerRecords: 0, tlkShareRecords: 1, zoneID: self.manateeZoneID)
1014 let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs")
1015 self.cuttlefishContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID) { error in
1016 XCTAssertNil(error, "error should be nil")
1017 joinWithBottleExpectation.fulfill()
1021 self.releaseCloudKitFetchHold()
1023 self.wait(for: [joinWithBottleExpectation], timeout: 100)
1025 let dumpCallback = self.expectation(description: "dumpCallback callback occurs")
1026 self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) {
1028 XCTAssertNotNil(dump, "dump should not be nil")
1029 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
1030 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
1031 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
1032 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
1033 let included = dynamicInfo!["included"] as? Array<String>
1034 XCTAssertNotNil(included, "included should not be nil")
1035 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
1036 dumpCallback.fulfill()
1038 self.wait(for: [dumpCallback], timeout: 10)
1040 self.verifyDatabaseMocks()
1041 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
1042 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
1044 //now call fetchviablebottles, we should get the uncached version
1045 let fetchUnCachedViableBottlesExpectation = self.expectation(description: "fetch UnCached ViableBottles")
1047 self.fakeCuttlefishServer.fetchViableBottlesListener = { request in
1048 self.fakeCuttlefishServer.fetchViableBottlesListener = nil
1049 fetchUnCachedViableBottlesExpectation.fulfill()
1052 let FetchAllViableBottles = self.expectation(description: "FetchAllViableBottles callback occurs")
1054 self.cuttlefishContext.rpcFetchAllViableBottles { viable, _, error in
1055 XCTAssertNil(error, "should be no error fetching viable bottles")
1056 XCTAssert(viable?.contains(bottle.bottleID) ?? false, "The bottle we're about to restore should be viable")
1057 FetchAllViableBottles.fulfill()
1059 self.wait(for: [FetchAllViableBottles], timeout: 10)
1060 self.wait(for: [fetchUnCachedViableBottlesExpectation], timeout: 10)
1062 let fetchViableExpectation = self.expectation(description: "fetchViableBottles callback occurs")
1063 self.cuttlefishContext.rpcFetchAllViableBottles { viable, _, error in
1064 XCTAssertNil(error, "should be no error fetching viable bottles")
1065 XCTAssert(viable?.contains(bottle.bottleID) ?? false, "The bottle we're about to restore should be viable")
1066 fetchViableExpectation.fulfill()
1068 self.wait(for: [fetchViableExpectation], timeout: 10)
1070 //now call fetchviablebottles, we should get the cached version
1071 let fetchViableBottlesExpectation = self.expectation(description: "fetch Cached ViableBottles")
1072 fetchViableBottlesExpectation.isInverted = true
1074 self.fakeCuttlefishServer.fetchViableBottlesListener = { request in
1075 self.fakeCuttlefishServer.fetchViableBottlesListener = nil
1076 fetchViableBottlesExpectation.fulfill()
1079 let fetchExpectation = self.expectation(description: "fetchExpectation callback occurs")
1081 self.cuttlefishContext.rpcFetchAllViableBottles { viable, _, error in
1082 XCTAssertNil(error, "should be no error fetching viable bottles")
1083 XCTAssert(viable?.contains(bottle.bottleID) ?? false, "The bottle we're about to restore should be viable")
1084 fetchExpectation.fulfill()
1086 self.wait(for: [fetchExpectation], timeout: 10)
1087 self.wait(for: [fetchViableBottlesExpectation], timeout: 10)
1090 func testViableBottleCachingAfterJoin() throws {
1091 let initiatorContextID = "initiator-context-id"
1092 let bottlerContext = self.makeInitiatorContext(contextID: initiatorContextID)
1094 bottlerContext.startOctagonStateMachine()
1095 let ckacctinfo = CKAccountInfo()
1096 ckacctinfo.accountStatus = .available
1097 ckacctinfo.hasValidCredentials = true
1098 ckacctinfo.accountPartition = .production
1100 bottlerContext.cloudkitAccountStateChange(nil, to: ckacctinfo)
1101 self.assertEnters(context: bottlerContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
1103 let clique: OTClique
1104 let bottlerotcliqueContext = OTConfigurationContext()
1105 bottlerotcliqueContext.context = initiatorContextID
1106 bottlerotcliqueContext.dsid = "1234"
1107 bottlerotcliqueContext.altDSID = self.mockAuthKit.altDSID!
1108 bottlerotcliqueContext.otControl = self.otControl
1110 clique = try OTClique.newFriends(withContextData: bottlerotcliqueContext, resetReason: .testGenerated)
1111 XCTAssertNotNil(clique, "Clique should not be nil")
1112 XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call")
1114 XCTFail("Shouldn't have errored making new friends: \(error)")
1118 self.assertEnters(context: bottlerContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
1119 self.assertConsidersSelfTrusted(context: bottlerContext)
1121 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
1122 XCTAssertNotNil(entropy, "entropy should not be nil")
1124 // Fake that this peer also created some TLKShares for itself
1125 self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID)
1126 try self.putSelfTLKShareInCloudKit(context: bottlerContext, zoneID: self.manateeZoneID)
1128 let bottle = self.fakeCuttlefishServer.state.bottles[0]
1130 self.cuttlefishContext.startOctagonStateMachine()
1131 self.startCKAccountStatusMock()
1132 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
1134 // Try to enforce that that CKKS doesn't know about the key hierarchy until Octagon asks it
1135 self.holdCloudKitFetches()
1137 // Note: CKKS will want to upload a TLKShare for its self
1138 self.expectCKModifyKeyRecords(0, currentKeyPointerRecords: 0, tlkShareRecords: 1, zoneID: self.manateeZoneID)
1140 let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs")
1141 self.cuttlefishContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID) { error in
1142 XCTAssertNil(error, "error should be nil")
1143 joinWithBottleExpectation.fulfill()
1147 self.releaseCloudKitFetchHold()
1149 self.wait(for: [joinWithBottleExpectation], timeout: 100)
1151 let dumpCallback = self.expectation(description: "dumpCallback callback occurs")
1152 self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) {
1154 XCTAssertNotNil(dump, "dump should not be nil")
1155 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
1156 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
1157 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
1158 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
1159 let included = dynamicInfo!["included"] as? Array<String>
1160 XCTAssertNotNil(included, "included should not be nil")
1161 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
1162 dumpCallback.fulfill()
1164 self.wait(for: [dumpCallback], timeout: 10)
1166 self.verifyDatabaseMocks()
1167 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
1168 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
1170 //now call fetchviablebottles, we should get the uncached version
1171 let fetchUnCachedViableBottlesExpectation = self.expectation(description: "fetch UnCached ViableBottles")
1173 self.fakeCuttlefishServer.fetchViableBottlesListener = { request in
1174 self.fakeCuttlefishServer.fetchViableBottlesListener = nil
1175 fetchUnCachedViableBottlesExpectation.fulfill()
1178 let FetchAllViableBottles = self.expectation(description: "FetchAllViableBottles callback occurs")
1180 self.cuttlefishContext.rpcFetchAllViableBottles { viable, _, error in
1181 XCTAssertNil(error, "should be no error fetching viable bottles")
1182 XCTAssert(viable?.contains(bottle.bottleID) ?? false, "The bottle we're about to restore should be viable")
1183 FetchAllViableBottles.fulfill()
1185 self.wait(for: [FetchAllViableBottles], timeout: 10)
1186 self.wait(for: [fetchUnCachedViableBottlesExpectation], timeout: 10)
1188 let fetchViableExpectation = self.expectation(description: "fetchViableBottles callback occurs")
1189 self.cuttlefishContext.rpcFetchAllViableBottles { viable, _, error in
1190 XCTAssertNil(error, "should be no error fetching viable bottles")
1191 XCTAssert(viable?.contains(bottle.bottleID) ?? false, "The bottle we're about to restore should be viable")
1192 fetchViableExpectation.fulfill()
1194 self.wait(for: [fetchViableExpectation], timeout: 10)
1196 //now call fetchviablebottles, we should get the cached version
1197 let fetchViableBottlesExpectation = self.expectation(description: "fetch Cached ViableBottles")
1198 fetchViableBottlesExpectation.isInverted = true
1200 self.fakeCuttlefishServer.fetchViableBottlesListener = { request in
1201 self.fakeCuttlefishServer.fetchViableBottlesListener = nil
1202 fetchViableBottlesExpectation.fulfill()
1205 let fetchExpectation = self.expectation(description: "fetchExpectation callback occurs")
1207 self.cuttlefishContext.rpcFetchAllViableBottles { viable, _, error in
1208 XCTAssertNil(error, "should be no error fetching viable bottles")
1209 XCTAssert(viable?.contains(bottle.bottleID) ?? false, "The bottle we're about to restore should be viable")
1210 fetchExpectation.fulfill()
1212 self.wait(for: [fetchExpectation], timeout: 10)
1213 self.wait(for: [fetchViableBottlesExpectation], timeout: 10)
1216 func testViableBottleReturns1Bottle() throws {
1217 let initiatorContextID = "initiator-context-id"
1218 let bottlerContext = self.makeInitiatorContext(contextID: initiatorContextID)
1220 bottlerContext.startOctagonStateMachine()
1221 let ckacctinfo = CKAccountInfo()
1222 ckacctinfo.accountStatus = .available
1223 ckacctinfo.hasValidCredentials = true
1224 ckacctinfo.accountPartition = .production
1226 bottlerContext.cloudkitAccountStateChange(nil, to: ckacctinfo)
1227 self.assertEnters(context: bottlerContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
1229 let clique: OTClique
1230 let bottlerotcliqueContext = OTConfigurationContext()
1231 bottlerotcliqueContext.context = initiatorContextID
1232 bottlerotcliqueContext.dsid = "1234"
1233 bottlerotcliqueContext.altDSID = self.mockAuthKit.altDSID!
1234 bottlerotcliqueContext.otControl = self.otControl
1236 clique = try OTClique.newFriends(withContextData: bottlerotcliqueContext, resetReason: .testGenerated)
1237 XCTAssertNotNil(clique, "Clique should not be nil")
1238 XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call")
1240 XCTFail("Shouldn't have errored making new friends: \(error)")
1244 self.assertEnters(context: bottlerContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
1245 self.assertConsidersSelfTrusted(context: bottlerContext)
1247 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
1248 XCTAssertNotNil(entropy, "entropy should not be nil")
1250 // Fake that this peer also created some TLKShares for itself
1251 self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID)
1252 try self.putSelfTLKShareInCloudKit(context: bottlerContext, zoneID: self.manateeZoneID)
1254 let bottle = self.fakeCuttlefishServer.state.bottles[0]
1256 self.cuttlefishContext.startOctagonStateMachine()
1257 self.startCKAccountStatusMock()
1258 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
1260 // Try to enforce that that CKKS doesn't know about the key hierarchy until Octagon asks it
1261 self.holdCloudKitFetches()
1263 // Note: CKKS will want to upload a TLKShare for its self
1264 self.expectCKModifyKeyRecords(0, currentKeyPointerRecords: 0, tlkShareRecords: 1, zoneID: self.manateeZoneID)
1266 let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs")
1267 self.cuttlefishContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID) { error in
1268 XCTAssertNil(error, "error should be nil")
1269 joinWithBottleExpectation.fulfill()
1273 self.releaseCloudKitFetchHold()
1275 self.wait(for: [joinWithBottleExpectation], timeout: 100)
1277 var egoPeerID: String?
1279 let dumpCallback = self.expectation(description: "dumpCallback callback occurs")
1280 self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) {
1282 XCTAssertNotNil(dump, "dump should not be nil")
1283 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
1284 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
1285 egoPeerID = egoSelf!["peerID"] as? String
1286 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
1287 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
1288 let included = dynamicInfo!["included"] as? Array<String>
1289 XCTAssertNotNil(included, "included should not be nil")
1290 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
1291 dumpCallback.fulfill()
1293 self.wait(for: [dumpCallback], timeout: 10)
1295 self.verifyDatabaseMocks()
1296 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
1297 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
1299 let bottles: [Bottle] = self.fakeCuttlefishServer.state.bottles
1300 var bottleToExclude: String?
1301 bottles.forEach { bottle in
1302 if bottle.peerID == egoPeerID {
1303 bottleToExclude = bottle.bottleID
1307 XCTAssertNotNil(bottleToExclude, "bottleToExclude should not be nil")
1309 //now call fetchviablebottles, we should get the uncached version
1310 var fetchUnCachedViableBottlesExpectation = self.expectation(description: "fetch UnCached ViableBottles")
1312 self.fakeCuttlefishServer.fetchViableBottlesListener = { request in
1313 self.fakeCuttlefishServer.fetchViableBottlesListener = nil
1314 fetchUnCachedViableBottlesExpectation.fulfill()
1317 self.fakeCuttlefishServer.fetchViableBottlesDontReturnBottleWithID = bottleToExclude
1318 var FetchAllViableBottles = self.expectation(description: "FetchAllViableBottles callback occurs")
1320 self.cuttlefishContext.rpcFetchAllViableBottles { viable, _, error in
1321 XCTAssertNil(error, "should be no error fetching viable bottles")
1322 XCTAssert(viable?.contains(bottle.bottleID) ?? false, "The bottle we're about to restore should be viable")
1323 FetchAllViableBottles.fulfill()
1325 self.wait(for: [FetchAllViableBottles], timeout: 10)
1326 self.wait(for: [fetchUnCachedViableBottlesExpectation], timeout: 10)
1328 //now call fetchviablebottles, we should get the uncached version
1329 fetchUnCachedViableBottlesExpectation = self.expectation(description: "fetch UnCached ViableBottles")
1331 self.fakeCuttlefishServer.fetchViableBottlesListener = { request in
1332 self.fakeCuttlefishServer.fetchViableBottlesListener = nil
1333 fetchUnCachedViableBottlesExpectation.fulfill()
1337 self.fakeCuttlefishServer.fetchViableBottlesDontReturnBottleWithID = bottleToExclude
1339 FetchAllViableBottles = self.expectation(description: "FetchAllViableBottles callback occurs")
1341 self.cuttlefishContext.rpcFetchAllViableBottles { viable, _, error in
1342 XCTAssertNil(error, "should be no error fetching viable bottles")
1343 XCTAssert(viable?.contains(bottle.bottleID) ?? false, "The bottle we're about to restore should be viable")
1344 FetchAllViableBottles.fulfill()
1346 self.wait(for: [FetchAllViableBottles], timeout: 10)
1347 self.wait(for: [fetchUnCachedViableBottlesExpectation], timeout: 10)