]> git.saurik.com Git - apple/security.git/blob - keychain/ot/tests/octagon/OctagonTests+SOS.swift
11961ed33c1ae90a75f38cc7c8a169b2a85920aa
[apple/security.git] / keychain / ot / tests / octagon / OctagonTests+SOS.swift
1 #if OCTAGON
2
3 class OctagonSOSTests: OctagonTestsBase {
4
5 func testSOSOctagonKeyConsistency() throws {
6 self.putFakeKeyHierarchiesInCloudKit()
7 self.putSelfTLKSharesInCloudKit()
8 self.saveTLKMaterialToKeychain()
9
10 self.startCKAccountStatusMock()
11
12 self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCInCircle)
13
14 XCTAssertTrue(OctagonPerformSOSUpgrade(), "SOS upgrade should be on")
15 self.cuttlefishContext.startOctagonStateMachine()
16
17 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
18 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
19
20 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
21
22 self.verifyDatabaseMocks()
23 self.waitForCKModifications()
24
25 self.assertSelfTLKSharesInCloudKit(context: self.cuttlefishContext)
26
27 let peerID = try self.cuttlefishContext.accountMetadataStore.getEgoPeerID()
28 XCTAssertNotNil(peerID, "Should have a peer ID")
29
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)
35
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)
40
41 self.cuttlefishContext.startOctagonStateMachine()
42 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
43
44 self.assertConsidersSelfTrustedCachedAccountStatus(context: self.cuttlefishContext)
45
46 let restartedPeerID = try self.cuttlefishContext.accountMetadataStore.getEgoPeerID()
47 XCTAssertNotNil(restartedPeerID, "Should have a peer ID after restarting")
48
49 XCTAssertEqual(peerID, restartedPeerID, "Should have the same peer ID after restarting")
50 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
51
52 self.verifyDatabaseMocks()
53 }
54
55 func testSOSOctagonKeyConsistencyLocked() throws {
56 self.putFakeKeyHierarchiesInCloudKit()
57 self.putSelfTLKSharesInCloudKit()
58 self.saveTLKMaterialToKeychain()
59
60 self.startCKAccountStatusMock()
61
62 self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCInCircle)
63
64 XCTAssertTrue(OctagonPerformSOSUpgrade(), "SOS upgrade should be on")
65 self.cuttlefishContext.startOctagonStateMachine()
66
67 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
68 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
69
70 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
71
72 self.verifyDatabaseMocks()
73 self.waitForCKModifications()
74
75 self.assertSelfTLKSharesInCloudKit(context: self.cuttlefishContext)
76
77 let peerID = try self.cuttlefishContext.accountMetadataStore.getEgoPeerID()
78 XCTAssertNotNil(peerID, "Should have a peer ID")
79
80 let newSOSPeer = createSOSPeer(peerID: peerID)
81 self.mockSOSAdapter.selfPeer = newSOSPeer
82
83 self.mockSOSAdapter.trustedPeers.add(newSOSPeer)
84
85 self.aksLockState = true
86 self.lockStateTracker.recheck()
87
88 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
89
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)
94
95 self.cuttlefishContext.startOctagonStateMachine()
96
97 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitForUnlock, within: 10 * NSEC_PER_SEC)
98 assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTrust, within: 10 * NSEC_PER_SEC)
99
100 self.assertAllCKKSViewsUpload(tlkShares: 2)
101 self.aksLockState = false
102 self.lockStateTracker.recheck()
103
104 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
105 self.assertConsidersSelfTrustedCachedAccountStatus(context: self.cuttlefishContext)
106
107 let restartedPeerID = try self.cuttlefishContext.accountMetadataStore.getEgoPeerID()
108 XCTAssertNotNil(restartedPeerID, "Should have a peer ID after restarting")
109
110 XCTAssertEqual(peerID, restartedPeerID, "Should have the same peer ID after restarting")
111
112 self.verifyDatabaseMocks()
113 self.waitForCKModifications()
114 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
115 }
116
117 func testSOSOctagonKeyConsistencySucceedsAfterUpdatingSOS() throws {
118 self.putFakeKeyHierarchiesInCloudKit()
119 self.putSelfTLKSharesInCloudKit()
120 self.saveTLKMaterialToKeychain()
121
122 self.startCKAccountStatusMock()
123
124 self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCInCircle)
125
126 XCTAssertTrue(OctagonPerformSOSUpgrade(), "SOS upgrade should be on")
127 self.cuttlefishContext.startOctagonStateMachine()
128
129 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
130 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
131
132 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
133
134 self.verifyDatabaseMocks()
135 self.waitForCKModifications()
136
137 self.assertSelfTLKSharesInCloudKit(context: self.cuttlefishContext)
138
139 let peerID = try self.cuttlefishContext.accountMetadataStore.getEgoPeerID()
140 XCTAssertNotNil(peerID, "Should have a peer ID")
141
142 let newSOSPeer = createSOSPeer(peerID: peerID)
143 self.mockSOSAdapter.selfPeer = newSOSPeer
144
145 self.mockSOSAdapter.trustedPeers.add(newSOSPeer)
146
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)
151
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)
156
157 self.cuttlefishContext.startOctagonStateMachine()
158
159 self.aksLockState = true
160 self.lockStateTracker.recheck()
161
162 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateWaitForUnlock, within: 10 * NSEC_PER_SEC)
163 assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTrust, within: 10 * NSEC_PER_SEC)
164
165 self.assertAllCKKSViewsUpload(tlkShares: 2)
166 self.aksLockState = false
167 self.lockStateTracker.recheck()
168
169 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
170 self.assertConsidersSelfTrustedCachedAccountStatus(context: self.cuttlefishContext)
171
172 let restartedPeerID = try self.cuttlefishContext.accountMetadataStore.getEgoPeerID()
173 XCTAssertNotNil(restartedPeerID, "Should have a peer ID after restarting")
174
175 XCTAssertEqual(peerID, restartedPeerID, "Should have the same peer ID after restarting")
176
177 self.verifyDatabaseMocks()
178 self.waitForCKModifications()
179 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
180 }
181
182 func testSOSPerformOctagonKeyConsistencyOnCircleChange() throws {
183 self.startCKAccountStatusMock()
184
185 // Octagon establishes its identity before SOS joins
186 self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCNotInCircle)
187
188 self.assertResetAndBecomeTrustedInDefaultContext()
189
190 let peerID = try self.cuttlefishContext.accountMetadataStore.getEgoPeerID()
191 XCTAssertNotNil(peerID, "Should have a peer ID")
192
193 // Now, SOS arrives
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()
198 }
199
200 // CKKS will upload itself new shares (for the newly trusted SOS self peer)
201 self.assertAllCKKSViewsUpload(tlkShares: 1)
202
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()
207
208 self.wait(for: [updateExpectation], timeout: 10)
209
210 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
211 self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
212 }
213
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
222
223 var clique: OTClique
224 do {
225 clique = try OTClique.newFriends(withContextData: recoverykeyotcliqueContext,
226 resetReason: .testGenerated)
227 XCTAssertNotNil(clique, "Clique should not be nil")
228 } catch {
229 XCTFail("Shouldn't have errored making new friends: \(error)")
230 throw error
231 }
232 do {
233 try clique.joinAfterRestore()
234 } catch {
235 XCTAssertNotNil(error, "error should not be nil")
236 }
237
238 do {
239 try clique.isLastFriend()
240 } catch {
241 XCTAssertNotNil(error, "error should not be nil")
242 }
243
244 do {
245 try clique.safariPasswordSyncingEnabled()
246 } catch {
247 XCTAssertNotNil(error, "error should not be nil")
248 }
249
250 do {
251 try clique.waitForInitialSync()
252 } catch {
253 XCTAssertNotNil(error, "error should not be nil")
254 }
255
256 clique.viewSet(Set(), disabledViews: Set())
257
258 do {
259 try clique.setUserCredentialsAndDSID("", password: Data())
260 } catch {
261 XCTAssertNotNil(error, "error should not be nil")
262 }
263
264 do {
265 try clique.tryUserCredentialsAndDSID("", password: Data())
266 } catch {
267 XCTAssertNotNil(error, "error should not be nil")
268 }
269
270 do {
271 try clique.peersHaveViewsEnabled([""])
272 } catch {
273 XCTAssertNotNil(error, "error should not be nil")
274 }
275
276 do {
277 try clique.requestToJoinCircle()
278 } catch {
279 XCTAssertNotNil(error, "error should not be nil")
280 }
281
282 clique.accountUserKeyAvailable()
283
284 do {
285 _ = try clique.copyViewUnawarePeerInfo()
286 } catch {
287 XCTAssertNotNil(error, "error should not be nil")
288 }
289 do {
290 _ = try clique.copyPeerPeerInfo()
291 } catch {
292 XCTAssertNotNil(error, "error should not be nil")
293 }
294 }
295
296 func testPreapproveSOSPeersWhenInCircle() throws {
297 self.startCKAccountStatusMock()
298
299 self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCInCircle)
300 let peer1Preapproval = TPHashBuilder.hash(with: .SHA256, of: self.mockSOSAdapter.selfPeer.publicSigningKey.encodeSubjectPublicKeyInfo())
301
302 let peer2SOSMockPeer = self.createSOSPeer(peerID: "peer2ID")
303 self.mockSOSAdapter.trustedPeers.add(peer2SOSMockPeer)
304 let peer2Preapproval = TPHashBuilder.hash(with: .SHA256, of: peer2SOSMockPeer.publicSigningKey.encodeSubjectPublicKeyInfo())
305
306 let peer3SOSMockPeer = self.createSOSPeer(peerID: "peer3ID")
307 self.mockSOSAdapter.trustedPeers.add(peer3SOSMockPeer)
308 let peer3Preapproval = TPHashBuilder.hash(with: .SHA256, of: peer3SOSMockPeer.publicSigningKey.encodeSubjectPublicKeyInfo())
309
310 let establishTwiceExpectation = self.expectation(description: "establish should be called twice")
311 establishTwiceExpectation.expectedFulfillmentCount = 2
312
313 self.fakeCuttlefishServer.establishListener = { request in
314 XCTAssertTrue(request.hasPeer, "establish request should have a peer")
315
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")
318
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")
321
322 establishTwiceExpectation.fulfill()
323 return nil
324 }
325
326 self.assertAllCKKSViewsUpload(tlkShares: 3)
327
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)
331
332 self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
333 self.verifyDatabaseMocks()
334
335 // And a reset does the right thing with preapprovals as well
336 do {
337 let arguments = OTConfigurationContext()
338 arguments.altDSID = try self.cuttlefishContext.authKitAdapter.primaryiCloudAccountAltDSID()
339 arguments.context = self.cuttlefishContext.contextID
340 arguments.otControl = self.otControl
341
342 let clique = try OTClique.newFriends(withContextData: arguments, resetReason: .testGenerated)
343 XCTAssertNotNil(clique, "Clique should not be nil")
344 } catch {
345 XCTFail("Shouldn't have errored making new friends: \(error)")
346 }
347
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()
351
352 self.wait(for: [establishTwiceExpectation], timeout: 1)
353
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")
358
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")
361
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")
364
365 peer2JoinExpectation.fulfill()
366
367 return nil
368 }
369
370 let peer2mockSOS = CKKSMockSOSPresentAdapter(selfPeer: peer2SOSMockPeer, trustedPeers: self.mockSOSAdapter.allPeers(), essential: false)
371 let peer2 = self.makeInitiatorContext(contextID: "peer2", authKitAdapter: self.mockAuthKit2, sosAdapter: peer2mockSOS)
372
373 peer2.startOctagonStateMachine()
374 self.assertEnters(context: peer2, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
375 self.assertConsidersSelfTrusted(context: peer2)
376
377 self.wait(for: [peer2JoinExpectation], timeout: 1)
378 }
379
380 func testDoNotPreapproveSOSPeerWhenOutOfCircle() throws {
381 self.startCKAccountStatusMock()
382
383 // SOS returns 'trusted' peers without actually being in-circle
384 // We don't want to preapprove those peers
385
386 self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCNotInCircle)
387 let peer1Preapproval = TPHashBuilder.hash(with: .SHA256, of: self.mockSOSAdapter.selfPeer.publicSigningKey.encodeSubjectPublicKeyInfo())
388
389 let peer2SOSMockPeer = self.createSOSPeer(peerID: "peer2ID")
390 self.mockSOSAdapter.trustedPeers.add(peer2SOSMockPeer)
391 let peer2Preapproval = TPHashBuilder.hash(with: .SHA256, of: peer2SOSMockPeer.publicSigningKey.encodeSubjectPublicKeyInfo())
392
393 let peer3SOSMockPeer = self.createSOSPeer(peerID: "peer3ID")
394 self.mockSOSAdapter.trustedPeers.add(peer3SOSMockPeer)
395 let peer3Preapproval = TPHashBuilder.hash(with: .SHA256, of: peer3SOSMockPeer.publicSigningKey.encodeSubjectPublicKeyInfo())
396
397 self.fakeCuttlefishServer.establishListener = { request in
398 XCTAssertTrue(request.hasPeer, "establish request should have a peer")
399
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")
402
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")
405
406 return nil
407 }
408
409 self.assertResetAndBecomeTrustedInDefaultContext()
410
411 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
412 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
413
414 self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
415
416 self.verifyDatabaseMocks()
417
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")
422
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")
425
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)")
428
429 peer2JoinExpectation.fulfill()
430
431 return nil
432 }
433
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)
437
438 peer2.startOctagonStateMachine()
439
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)
443
444 self.wait(for: [peer2JoinExpectation], timeout: 1)
445 }
446
447 func testRespondToNewOctagonPeerWhenUpdatingPreapprovedKeys() throws {
448 self.startCKAccountStatusMock()
449
450 self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCInCircle)
451 let peer1Preapproval = TPHashBuilder.hash(with: .SHA256, of: self.mockSOSAdapter.selfPeer.publicSigningKey.encodeSubjectPublicKeyInfo())
452
453 let peer2SOSMockPeer = self.createSOSPeer(peerID: "peer2ID")
454 self.mockSOSAdapter.trustedPeers.add(peer2SOSMockPeer)
455 let peer2Preapproval = TPHashBuilder.hash(with: .SHA256, of: peer2SOSMockPeer.publicSigningKey.encodeSubjectPublicKeyInfo())
456
457 self.assertAllCKKSViewsUpload(tlkShares: 2)
458
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()
463
464 self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
465 self.verifyDatabaseMocks()
466
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)
471
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())
476
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")
480
481 let newDynamicInfo = request.dynamicInfoAndSig.dynamicInfo()
482
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")
486
487 self.fakeCuttlefishServer.updateListener = nil
488 updateKeysExpectation.fulfill()
489
490 return nil
491 }
492
493 // And we'll send TLKShares to the new SOS peer and the new Octagon peer
494 self.assertAllCKKSViewsUpload(tlkShares: 2)
495
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)
500
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()
504
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")
509 }
510 }
511
512 #endif