3 class OctagonSOSTests: OctagonTestsBase {
5 func testSOSOctagonKeyConsistency() throws {
6 self.putFakeKeyHierarchiesInCloudKit()
7 self.putSelfTLKSharesInCloudKit()
8 self.saveTLKMaterialToKeychain()
10 self.startCKAccountStatusMock()
12 self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCInCircle)
14 XCTAssertTrue(OctagonPerformSOSUpgrade(), "SOS upgrade should be on")
15 self.cuttlefishContext.startOctagonStateMachine()
17 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
18 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
20 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
22 self.verifyDatabaseMocks()
23 self.waitForCKModifications()
25 self.assertSelfTLKSharesInCloudKit(context: self.cuttlefishContext)
27 let peerID = try self.cuttlefishContext.accountMetadataStore.getEgoPeerID()
28 XCTAssertNotNil(peerID, "Should have a peer ID")
30 // CKKS will upload new TLKShares
31 self.assertAllCKKSViewsUpload(tlkShares: 2)
32 let newSOSPeer = createSOSPeer(peerID: peerID)
33 self.mockSOSAdapter.selfPeer = newSOSPeer
34 self.mockSOSAdapter.trustedPeers.add(newSOSPeer)
36 // Now restart the context
37 self.manager.removeContext(forContainerName: OTCKContainerName, contextID: OTDefaultContext)
38 self.restartCKKSViews()
39 self.cuttlefishContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext)
41 self.cuttlefishContext.startOctagonStateMachine()
42 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
44 self.assertConsidersSelfTrustedCachedAccountStatus(context: self.cuttlefishContext)
46 let restartedPeerID = try self.cuttlefishContext.accountMetadataStore.getEgoPeerID()
47 XCTAssertNotNil(restartedPeerID, "Should have a peer ID after restarting")
49 XCTAssertEqual(peerID, restartedPeerID, "Should have the same peer ID after restarting")
50 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
52 self.verifyDatabaseMocks()
55 func testSOSOctagonKeyConsistencyLocked() throws {
56 self.putFakeKeyHierarchiesInCloudKit()
57 self.putSelfTLKSharesInCloudKit()
58 self.saveTLKMaterialToKeychain()
60 self.startCKAccountStatusMock()
62 self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCInCircle)
64 XCTAssertTrue(OctagonPerformSOSUpgrade(), "SOS upgrade should be on")
65 self.cuttlefishContext.startOctagonStateMachine()
67 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
68 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
70 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
72 self.verifyDatabaseMocks()
73 self.waitForCKModifications()
75 self.assertSelfTLKSharesInCloudKit(context: self.cuttlefishContext)
77 let peerID = try self.cuttlefishContext.accountMetadataStore.getEgoPeerID()
78 XCTAssertNotNil(peerID, "Should have a peer ID")
80 let newSOSPeer = createSOSPeer(peerID: peerID)
81 self.mockSOSAdapter.selfPeer = newSOSPeer
83 self.mockSOSAdapter.trustedPeers.add(newSOSPeer)
85 self.aksLockState = true
86 self.lockStateTracker.recheck()
88 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
90 // Now restart the context
91 self.manager.removeContext(forContainerName: OTCKContainerName, contextID: OTDefaultContext)
92 self.restartCKKSViews()
93 self.cuttlefishContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext)
95 self.cuttlefishContext.startOctagonStateMachine()
97 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitForUnlock, within: 10 * NSEC_PER_SEC)
98 assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTrust, within: 10 * NSEC_PER_SEC)
100 self.assertAllCKKSViewsUpload(tlkShares: 2)
101 self.aksLockState = false
102 self.lockStateTracker.recheck()
104 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
105 self.assertConsidersSelfTrustedCachedAccountStatus(context: self.cuttlefishContext)
107 let restartedPeerID = try self.cuttlefishContext.accountMetadataStore.getEgoPeerID()
108 XCTAssertNotNil(restartedPeerID, "Should have a peer ID after restarting")
110 XCTAssertEqual(peerID, restartedPeerID, "Should have the same peer ID after restarting")
112 self.verifyDatabaseMocks()
113 self.waitForCKModifications()
114 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
117 func testSOSOctagonKeyConsistencySucceedsAfterUpdatingSOS() throws {
118 self.putFakeKeyHierarchiesInCloudKit()
119 self.putSelfTLKSharesInCloudKit()
120 self.saveTLKMaterialToKeychain()
122 self.startCKAccountStatusMock()
124 self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCInCircle)
126 XCTAssertTrue(OctagonPerformSOSUpgrade(), "SOS upgrade should be on")
127 self.cuttlefishContext.startOctagonStateMachine()
129 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
130 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
132 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
134 self.verifyDatabaseMocks()
135 self.waitForCKModifications()
137 self.assertSelfTLKSharesInCloudKit(context: self.cuttlefishContext)
139 let peerID = try self.cuttlefishContext.accountMetadataStore.getEgoPeerID()
140 XCTAssertNotNil(peerID, "Should have a peer ID")
142 let newSOSPeer = createSOSPeer(peerID: peerID)
143 self.mockSOSAdapter.selfPeer = newSOSPeer
145 self.mockSOSAdapter.trustedPeers.add(newSOSPeer)
147 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
148 self.assertSelfTLKSharesInCloudKit(context: self.cuttlefishContext)
149 self.assertConsidersSelfTrustedCachedAccountStatus(context: self.cuttlefishContext)
150 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
152 // Now restart the context
153 self.manager.removeContext(forContainerName: OTCKContainerName, contextID: OTDefaultContext)
154 self.restartCKKSViews()
155 self.cuttlefishContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext)
157 self.cuttlefishContext.startOctagonStateMachine()
159 self.aksLockState = true
160 self.lockStateTracker.recheck()
162 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitForUnlock, within: 10 * NSEC_PER_SEC)
163 assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTrust, within: 10 * NSEC_PER_SEC)
165 self.assertAllCKKSViewsUpload(tlkShares: 2)
166 self.aksLockState = false
167 self.lockStateTracker.recheck()
169 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
170 self.assertConsidersSelfTrustedCachedAccountStatus(context: self.cuttlefishContext)
172 let restartedPeerID = try self.cuttlefishContext.accountMetadataStore.getEgoPeerID()
173 XCTAssertNotNil(restartedPeerID, "Should have a peer ID after restarting")
175 XCTAssertEqual(peerID, restartedPeerID, "Should have the same peer ID after restarting")
177 self.verifyDatabaseMocks()
178 self.waitForCKModifications()
179 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
182 func testSOSPerformOctagonKeyConsistencyOnCircleChange() throws {
183 self.startCKAccountStatusMock()
185 // Octagon establishes its identity before SOS joins
186 self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCNotInCircle)
188 self.assertResetAndBecomeTrustedInDefaultContext()
190 let peerID = try self.cuttlefishContext.accountMetadataStore.getEgoPeerID()
191 XCTAssertNotNil(peerID, "Should have a peer ID")
194 let updateExpectation = self.expectation(description: "Octagon should inform SOS of its keys")
195 self.mockSOSAdapter.updateOctagonKeySetListener = { _ in
196 // Don't currently check the key set at all here
197 updateExpectation.fulfill()
200 // CKKS will upload itself new shares (for the newly trusted SOS self peer)
201 self.assertAllCKKSViewsUpload(tlkShares: 1)
203 self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCInCircle)
204 // Note: this should probably be sendSelfPeerChangedUpdate, but we don't have great fidelity around which peer
205 // actually changed. So, just use this channel for now
206 self.mockSOSAdapter.sendTrustedPeerSetChangedUpdate()
208 self.wait(for: [updateExpectation], timeout: 10)
210 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
211 self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
214 func testDisablingSOSFeatureFlag() throws {
215 self.startCKAccountStatusMock()
216 OctagonSetSOSFeatureEnabled(false)
217 let recoverykeyotcliqueContext = OTConfigurationContext()
218 recoverykeyotcliqueContext.context = "recoveryContext"
219 recoverykeyotcliqueContext.dsid = "1234"
220 recoverykeyotcliqueContext.altDSID = self.mockAuthKit.altDSID!
221 recoverykeyotcliqueContext.otControl = self.otControl
225 clique = try OTClique.newFriends(withContextData: recoverykeyotcliqueContext,
226 resetReason: .testGenerated)
227 XCTAssertNotNil(clique, "Clique should not be nil")
229 XCTFail("Shouldn't have errored making new friends: \(error)")
233 try clique.joinAfterRestore()
235 XCTAssertNotNil(error, "error should not be nil")
239 try clique.isLastFriend()
241 XCTAssertNotNil(error, "error should not be nil")
245 try clique.safariPasswordSyncingEnabled()
247 XCTAssertNotNil(error, "error should not be nil")
251 try clique.waitForInitialSync()
253 XCTAssertNotNil(error, "error should not be nil")
256 clique.viewSet(Set(), disabledViews: Set())
259 try clique.setUserCredentialsAndDSID("", password: Data())
261 XCTAssertNotNil(error, "error should not be nil")
265 try clique.tryUserCredentialsAndDSID("", password: Data())
267 XCTAssertNotNil(error, "error should not be nil")
271 try clique.peersHaveViewsEnabled([""])
273 XCTAssertNotNil(error, "error should not be nil")
277 try clique.requestToJoinCircle()
279 XCTAssertNotNil(error, "error should not be nil")
282 clique.accountUserKeyAvailable()
285 _ = try clique.copyViewUnawarePeerInfo()
287 XCTAssertNotNil(error, "error should not be nil")
290 _ = try clique.copyPeerPeerInfo()
292 XCTAssertNotNil(error, "error should not be nil")
296 func testPreapproveSOSPeersWhenInCircle() throws {
297 self.startCKAccountStatusMock()
299 self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCInCircle)
300 let peer1Preapproval = TPHashBuilder.hash(with: .SHA256, of: self.mockSOSAdapter.selfPeer.publicSigningKey.encodeSubjectPublicKeyInfo())
302 let peer2SOSMockPeer = self.createSOSPeer(peerID: "peer2ID")
303 self.mockSOSAdapter.trustedPeers.add(peer2SOSMockPeer)
304 let peer2Preapproval = TPHashBuilder.hash(with: .SHA256, of: peer2SOSMockPeer.publicSigningKey.encodeSubjectPublicKeyInfo())
306 let peer3SOSMockPeer = self.createSOSPeer(peerID: "peer3ID")
307 self.mockSOSAdapter.trustedPeers.add(peer3SOSMockPeer)
308 let peer3Preapproval = TPHashBuilder.hash(with: .SHA256, of: peer3SOSMockPeer.publicSigningKey.encodeSubjectPublicKeyInfo())
310 let establishTwiceExpectation = self.expectation(description: "establish should be called twice")
311 establishTwiceExpectation.expectedFulfillmentCount = 2
313 self.fakeCuttlefishServer.establishListener = { request in
314 XCTAssertTrue(request.hasPeer, "establish request should have a peer")
316 let newDynamicInfo = TPPeerDynamicInfo(data: request.peer.dynamicInfoAndSig.peerDynamicInfo, sig: request.peer.dynamicInfoAndSig.sig)
317 XCTAssertNotNil(newDynamicInfo, "should be able to make a dynamicInfo from protobuf")
319 XCTAssertTrue(newDynamicInfo?.preapprovals.contains(peer2Preapproval) ?? false, "Fake peer 2 should be preapproved")
320 XCTAssertTrue(newDynamicInfo?.preapprovals.contains(peer3Preapproval) ?? false, "Fake peer 3 should be preapproved")
322 establishTwiceExpectation.fulfill()
326 self.assertAllCKKSViewsUpload(tlkShares: 3)
328 // Just starting the state machine is sufficient; it should perform an SOS upgrade
329 self.cuttlefishContext.startOctagonStateMachine()
330 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
332 self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
333 self.verifyDatabaseMocks()
335 // And a reset does the right thing with preapprovals as well
337 let arguments = OTConfigurationContext()
338 arguments.altDSID = try self.cuttlefishContext.authKitAdapter.primaryiCloudAccountAltDSID()
339 arguments.context = self.cuttlefishContext.contextID
340 arguments.otControl = self.otControl
342 let clique = try OTClique.newFriends(withContextData: arguments, resetReason: .testGenerated)
343 XCTAssertNotNil(clique, "Clique should not be nil")
345 XCTFail("Shouldn't have errored making new friends: \(error)")
348 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
349 self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
350 self.verifyDatabaseMocks()
352 self.wait(for: [establishTwiceExpectation], timeout: 1)
354 // And do we do the right thing when joining via SOS preapproval?
355 let peer2JoinExpectation = self.expectation(description: "join called")
356 self.fakeCuttlefishServer.joinListener = { request in
357 XCTAssertTrue(request.hasPeer, "establish request should have a peer")
359 let newDynamicInfo = TPPeerDynamicInfo(data: request.peer.dynamicInfoAndSig.peerDynamicInfo, sig: request.peer.dynamicInfoAndSig.sig)
360 XCTAssertNotNil(newDynamicInfo, "should be able to make a dynamicInfo from protobuf")
362 XCTAssertFalse(newDynamicInfo?.preapprovals.contains(peer1Preapproval) ?? false, "Fake peer 1 should NOT be preapproved by peer2 (as it's already in Octagon)")
363 XCTAssertTrue(newDynamicInfo?.preapprovals.contains(peer3Preapproval) ?? false, "Fake peer 3 should be preapproved by peer2")
365 peer2JoinExpectation.fulfill()
370 let peer2mockSOS = CKKSMockSOSPresentAdapter(selfPeer: peer2SOSMockPeer, trustedPeers: self.mockSOSAdapter.allPeers(), essential: false)
371 let peer2 = self.makeInitiatorContext(contextID: "peer2", authKitAdapter: self.mockAuthKit2, sosAdapter: peer2mockSOS)
373 peer2.startOctagonStateMachine()
374 self.assertEnters(context: peer2, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
375 self.assertConsidersSelfTrusted(context: peer2)
377 self.wait(for: [peer2JoinExpectation], timeout: 1)
380 func testDoNotPreapproveSOSPeerWhenOutOfCircle() throws {
381 self.startCKAccountStatusMock()
383 // SOS returns 'trusted' peers without actually being in-circle
384 // We don't want to preapprove those peers
386 self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCNotInCircle)
387 let peer1Preapproval = TPHashBuilder.hash(with: .SHA256, of: self.mockSOSAdapter.selfPeer.publicSigningKey.encodeSubjectPublicKeyInfo())
389 let peer2SOSMockPeer = self.createSOSPeer(peerID: "peer2ID")
390 self.mockSOSAdapter.trustedPeers.add(peer2SOSMockPeer)
391 let peer2Preapproval = TPHashBuilder.hash(with: .SHA256, of: peer2SOSMockPeer.publicSigningKey.encodeSubjectPublicKeyInfo())
393 let peer3SOSMockPeer = self.createSOSPeer(peerID: "peer3ID")
394 self.mockSOSAdapter.trustedPeers.add(peer3SOSMockPeer)
395 let peer3Preapproval = TPHashBuilder.hash(with: .SHA256, of: peer3SOSMockPeer.publicSigningKey.encodeSubjectPublicKeyInfo())
397 self.fakeCuttlefishServer.establishListener = { request in
398 XCTAssertTrue(request.hasPeer, "establish request should have a peer")
400 let newDynamicInfo = TPPeerDynamicInfo(data: request.peer.dynamicInfoAndSig.peerDynamicInfo, sig: request.peer.dynamicInfoAndSig.sig)
401 XCTAssertNotNil(newDynamicInfo, "should be able to make a dynamicInfo from protobuf")
403 XCTAssertFalse(newDynamicInfo?.preapprovals.contains(peer2Preapproval) ?? false, "Fake peer 2 should not be preapproved")
404 XCTAssertFalse(newDynamicInfo?.preapprovals.contains(peer3Preapproval) ?? false, "Fake peer 3 should not be preapproved")
409 self.assertResetAndBecomeTrustedInDefaultContext()
411 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
412 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
414 self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
416 self.verifyDatabaseMocks()
418 // And do we do the right thing when joining via bottle?
419 let peer2JoinExpectation = self.expectation(description: "join called")
420 self.fakeCuttlefishServer.joinListener = { request in
421 XCTAssertTrue(request.hasPeer, "establish request should have a peer")
423 let newDynamicInfo = TPPeerDynamicInfo(data: request.peer.dynamicInfoAndSig.peerDynamicInfo, sig: request.peer.dynamicInfoAndSig.sig)
424 XCTAssertNotNil(newDynamicInfo, "should be able to make a dynamicInfo from protobuf")
426 XCTAssertFalse(newDynamicInfo?.preapprovals.contains(peer1Preapproval) ?? false, "Fake peer 1 should NOT be preapproved by peer2 (as it's not in SOS)")
427 XCTAssertFalse(newDynamicInfo?.preapprovals.contains(peer3Preapproval) ?? false, "Fake peer 3 should not be preapproved by peer2 (as it's not in SOS)")
429 peer2JoinExpectation.fulfill()
434 let peer2mockSOS = CKKSMockSOSPresentAdapter(selfPeer: peer2SOSMockPeer, trustedPeers: self.mockSOSAdapter.allPeers(), essential: false)
435 peer2mockSOS.circleStatus = SOSCCStatus(kSOSCCNotInCircle)
436 let peer2 = self.makeInitiatorContext(contextID: "peer2", authKitAdapter: self.mockAuthKit2, sosAdapter: peer2mockSOS)
438 peer2.startOctagonStateMachine()
440 _ = self.assertJoinViaEscrowRecovery(joiningContext: peer2, sponsor: self.cuttlefishContext)
441 self.assertEnters(context: peer2, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
442 self.assertConsidersSelfTrusted(context: peer2)
444 self.wait(for: [peer2JoinExpectation], timeout: 1)
447 func testRespondToNewOctagonPeerWhenUpdatingPreapprovedKeys() throws {
448 self.startCKAccountStatusMock()
450 self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCInCircle)
451 let peer1Preapproval = TPHashBuilder.hash(with: .SHA256, of: self.mockSOSAdapter.selfPeer.publicSigningKey.encodeSubjectPublicKeyInfo())
453 let peer2SOSMockPeer = self.createSOSPeer(peerID: "peer2ID")
454 self.mockSOSAdapter.trustedPeers.add(peer2SOSMockPeer)
455 let peer2Preapproval = TPHashBuilder.hash(with: .SHA256, of: peer2SOSMockPeer.publicSigningKey.encodeSubjectPublicKeyInfo())
457 self.assertAllCKKSViewsUpload(tlkShares: 2)
459 // Just starting the state machine is sufficient; it should perform an SOS upgrade
460 self.cuttlefishContext.startOctagonStateMachine()
461 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
462 let peer1ID = try self.cuttlefishContext.accountMetadataStore.getEgoPeerID()
464 self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
465 self.verifyDatabaseMocks()
467 // Another peer arrives, but we miss the Octagon push
468 let peer2mockSOS = CKKSMockSOSPresentAdapter(selfPeer: peer2SOSMockPeer, trustedPeers: self.mockSOSAdapter.allPeers(), essential: false)
469 let joiningContext = self.makeInitiatorContext(contextID: "joiner", authKitAdapter: self.mockAuthKit2, sosAdapter: peer2mockSOS)
470 let peer2ID = self.assertJoinViaEscrowRecovery(joiningContext: joiningContext, sponsor: self.cuttlefishContext)
472 // Now, SOS updates its key list: we should update our preapproved keys (and then also trust the newly-joined peer)
473 let peer3SOSMockPeer = self.createSOSPeer(peerID: "peer3ID")
474 self.mockSOSAdapter.trustedPeers.add(peer3SOSMockPeer)
475 let peer3Preapproval = TPHashBuilder.hash(with: .SHA256, of: peer3SOSMockPeer.publicSigningKey.encodeSubjectPublicKeyInfo())
477 let updateKeysExpectation = self.expectation(description: "UpdateTrust should fire (once)")
478 self.fakeCuttlefishServer.updateListener = { [unowned self] request in
479 XCTAssertEqual(request.peerID, peer1ID, "UpdateTrust should be for peer1")
481 let newDynamicInfo = request.dynamicInfoAndSig.dynamicInfo()
483 XCTAssertFalse(newDynamicInfo.preapprovals.contains(peer1Preapproval), "Fake peer 1 should NOT be preapproved by peer1 (as it's its own keys)")
484 XCTAssertTrue(newDynamicInfo.preapprovals.contains(peer2Preapproval), "Fake peer 2 should be preapproved by original peer")
485 XCTAssertTrue(newDynamicInfo.preapprovals.contains(peer3Preapproval), "Fake peer 3 should be preapproved by original peer")
487 self.fakeCuttlefishServer.updateListener = nil
488 updateKeysExpectation.fulfill()
493 // And we'll send TLKShares to the new SOS peer and the new Octagon peer
494 self.assertAllCKKSViewsUpload(tlkShares: 2)
496 // to avoid CKKS race conditions (wherein it uploads each TLKShare in its own operation), send the SOS notification only to the Octagon context
497 //self.mockSOSAdapter.sendTrustedPeerSetChangedUpdate()
498 self.cuttlefishContext.trustedPeerSetChanged(self.mockSOSAdapter)
499 self.wait(for: [updateKeysExpectation], timeout: 10)
501 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
502 self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
503 self.verifyDatabaseMocks()
505 XCTAssertTrue(self.fakeCuttlefishServer.assertCuttlefishState(FakeCuttlefishAssertion(peer: peer1ID, opinion: .trusts, target: peer1ID)),
506 "peer 1 should trust peer 1")
507 XCTAssertTrue(self.fakeCuttlefishServer.assertCuttlefishState(FakeCuttlefishAssertion(peer: peer1ID, opinion: .trusts, target: peer2ID)),
508 "peer 1 should trust peer 2")