5 class OctagonForwardCompatibilityTests: OctagonTestsBase {
6 func testApprovePeerWithNewPolicy() throws {
7 self.startCKAccountStatusMock()
8 let peer1ID = self.assertResetAndBecomeTrustedInDefaultContext()
10 // Now, we'll approve a new peer with a new policy! First, make that new policy.
11 let currentPolicyOptional = builtInPolicyDocuments().filter { $0.version.versionNumber == prevailingPolicyVersion.versionNumber }.first
12 XCTAssertNotNil(currentPolicyOptional, "Should have one current policy")
13 let currentPolicy = currentPolicyOptional!
15 let newPolicy = currentPolicy.clone(withVersionNumber: currentPolicy.version.versionNumber + 1)!
16 self.fakeCuttlefishServer.policyOverlay.append(newPolicy)
18 let peer2ContextID = "asdf"
20 // Assist the other client here: it'll likely already have the policy built-in
21 let fetchExpectation = self.expectation(description: "fetch callback occurs")
22 self.tphClient.fetchPolicyDocuments(withContainer: OTCKContainerName,
23 context: peer2ContextID,
24 versions: Set([newPolicy.version])) { _, error in
25 XCTAssertNil(error, "Should have no error")
26 fetchExpectation.fulfill()
28 self.wait(for: [fetchExpectation], timeout: 10)
30 var peer2ID: String = "not initialized"
32 let prepareExpectation = self.expectation(description: "prepare callback occurs")
33 let vouchExpectation = self.expectation(description: "vouch callback occurs")
34 let joinExpectation = self.expectation(description: "join callback occurs")
35 let serverJoinExpectation = self.expectation(description: "joinWithVoucher is called")
37 self.tphClient.prepare(withContainer: OTCKContainerName,
38 context: peer2ContextID,
40 machineID: self.mockAuthKit2.currentMachineID,
41 bottleSalt: self.mockAuthKit2.altDSID!,
42 bottleID: "why-is-this-nonnil",
43 modelID: self.mockDeviceInfo.modelID(),
44 deviceName: "new-policy-peer",
46 osVersion: "something",
47 policyVersion: newPolicy.version,
49 signingPrivKeyPersistentRef: nil,
50 encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, _, _, error in
51 XCTAssertNil(error, "Should be no error preparing the second peer")
52 XCTAssertNotNil(peerID, "Should have a peerID")
55 XCTAssertNotNil(stableInfo, "Should have a stable info")
56 XCTAssertNotNil(stableInfoSig, "Should have a stable info signature")
58 let newStableInfo = TPPeerStableInfo(data: stableInfo!, sig: stableInfoSig!)
59 XCTAssertNotNil(newStableInfo, "should be able to make a stableInfo info from protobuf")
61 XCTAssertEqual(newStableInfo?.frozenPolicyVersion, frozenPolicyVersion, "Frozen policy version in new identity should match frozen policy version")
62 XCTAssertEqual(newStableInfo?.flexiblePolicyVersion, newPolicy.version, "Flexible policy version in new identity should match new policy version")
64 self.tphClient.vouch(withContainer: self.cuttlefishContext.containerName,
65 context: self.cuttlefishContext.contextID,
67 permanentInfo: permanentInfo!,
68 permanentInfoSig: permanentInfoSig!,
69 stableInfo: stableInfo!,
70 stableInfoSig: stableInfoSig!,
71 ckksKeys: []) { voucher, voucherSig, error in
72 XCTAssertNil(error, "Should be no error vouching")
73 XCTAssertNotNil(voucher, "Should have a voucher")
74 XCTAssertNotNil(voucherSig, "Should have a voucher signature")
76 self.fakeCuttlefishServer.joinListener = { joinRequest in
77 XCTAssertEqual(peer2ID, joinRequest.peer.peerID, "joinWithVoucher request should be for peer 2")
78 XCTAssertTrue(joinRequest.peer.hasStableInfoAndSig, "Joining peer should have a stable info")
79 let newStableInfo = joinRequest.peer.stableInfoAndSig.stableInfo()
81 XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Frozen policy version in new identity should match frozen policy version")
82 XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicy.version, "Flexible policy version in new identity should match new policy version (as provided by new peer)")
84 serverJoinExpectation.fulfill()
88 self.tphClient.join(withContainer: OTCKContainerName,
89 context: peer2ContextID,
90 voucherData: voucher!,
91 voucherSig: voucherSig!,
94 preapprovedKeys: []) { peerID, _, _, _, error in
95 XCTAssertNil(error, "Should be no error joining")
96 XCTAssertNotNil(peerID, "Should have a peerID")
97 joinExpectation.fulfill()
99 vouchExpectation.fulfill()
101 prepareExpectation.fulfill()
103 self.wait(for: [prepareExpectation, vouchExpectation, joinExpectation, serverJoinExpectation], timeout: 10)
105 // Then, after the remote peer joins, the original peer should realize it trusts the new peer and update its own stableinfo to use the new policy
106 let updateTrustExpectation = self.expectation(description: "updateTrust")
107 self.fakeCuttlefishServer.updateListener = { request in
108 XCTAssertEqual(peer1ID, request.peerID, "updateTrust request should be for peer 1")
109 let newDynamicInfo = request.dynamicInfoAndSig.dynamicInfo()
110 XCTAssert(newDynamicInfo.includedPeerIDs.contains(peer2ID), "Peer1 should trust peer2")
112 let newStableInfo = request.stableInfoAndSig.stableInfo()
114 XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version")
115 XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicy.version, "Prevailing policy version in peer should match new policy version")
117 updateTrustExpectation.fulfill()
121 self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext)
122 self.wait(for: [updateTrustExpectation], timeout: 10)
123 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
126 func testRejectVouchingForPeerWithUnknownNewPolicy() throws {
127 self.startCKAccountStatusMock()
128 _ = self.assertResetAndBecomeTrustedInDefaultContext()
130 // Now, a new peer joins with a policy we can't fetch
131 let currentPolicyOptional = builtInPolicyDocuments().filter { $0.version.versionNumber == prevailingPolicyVersion.versionNumber }.first
132 XCTAssertNotNil(currentPolicyOptional, "Should have one current policy")
133 let currentPolicy = currentPolicyOptional!
135 let newPolicy = currentPolicy.clone(withVersionNumber: currentPolicy.version.versionNumber + 1)!
137 let peer2ContextID = "asdf"
139 // Assist the other client here: it'll likely already have this built-in
140 self.fakeCuttlefishServer.policyOverlay.append(newPolicy)
142 let fetchExpectation = self.expectation(description: "fetch callback occurs")
143 self.tphClient.fetchPolicyDocuments(withContainer: OTCKContainerName,
144 context: peer2ContextID,
145 versions: Set([newPolicy.version])) { _, error in
146 XCTAssertNil(error, "Should have no error")
147 fetchExpectation.fulfill()
149 self.wait(for: [fetchExpectation], timeout: 10)
151 // Remove the policy, now that peer2 has it
152 self.fakeCuttlefishServer.policyOverlay.removeAll()
154 let prepareExpectation = self.expectation(description: "prepare callback occurs")
155 let vouchExpectation = self.expectation(description: "vouch callback occurs")
157 self.tphClient.prepare(withContainer: OTCKContainerName,
158 context: peer2ContextID,
160 machineID: self.mockAuthKit2.currentMachineID,
161 bottleSalt: self.mockAuthKit2.altDSID!,
162 bottleID: "why-is-this-nonnil",
163 modelID: self.mockDeviceInfo.modelID(),
164 deviceName: "new-policy-peer",
165 serialNumber: "1234",
166 osVersion: "something",
167 policyVersion: newPolicy.version,
169 signingPrivKeyPersistentRef: nil,
170 encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, _, _, error in
171 XCTAssertNil(error, "Should be no error preparing the second peer")
172 XCTAssertNotNil(peerID, "Should have a peerID")
174 XCTAssertNotNil(stableInfo, "Should have a stable info")
175 XCTAssertNotNil(stableInfoSig, "Should have a stable info signature")
177 let newStableInfo = TPPeerStableInfo(data: stableInfo!, sig: stableInfoSig!)
178 XCTAssertNotNil(newStableInfo, "should be able to make a stableInfo info from protobuf")
180 XCTAssertEqual(newStableInfo?.frozenPolicyVersion, frozenPolicyVersion, "Frozen policy version in new identity should match frozen policy version")
181 XCTAssertEqual(newStableInfo?.flexiblePolicyVersion, newPolicy.version, "Flexible policy version in new identity should match new policy version")
183 self.tphClient.vouch(withContainer: self.cuttlefishContext.containerName,
184 context: self.cuttlefishContext.contextID,
186 permanentInfo: permanentInfo!,
187 permanentInfoSig: permanentInfoSig!,
188 stableInfo: stableInfo!,
189 stableInfoSig: stableInfoSig!,
190 ckksKeys: []) { voucher, voucherSig, error in
191 XCTAssertNotNil(error, "should be an error vouching for a peer with an unknown policy")
192 XCTAssertNil(voucher, "Should have no voucher")
193 XCTAssertNil(voucherSig, "Should have no voucher signature")
195 vouchExpectation.fulfill()
197 prepareExpectation.fulfill()
199 self.wait(for: [prepareExpectation, vouchExpectation], timeout: 10)
202 func testIgnoreAlreadyJoinedPeerWithUnknownNewPolicy() throws {
203 self.startCKAccountStatusMock()
204 let peer1ID = self.assertResetAndBecomeTrustedInDefaultContext()
206 // Now, a new peer joins with a policy we can't fetch
207 let currentPolicyOptional = builtInPolicyDocuments().filter { $0.version.versionNumber == prevailingPolicyVersion.versionNumber }.first
208 XCTAssertNotNil(currentPolicyOptional, "Should have one current policy")
209 let currentPolicy = currentPolicyOptional!
211 let newPolicy = currentPolicy.clone(withVersionNumber: currentPolicy.version.versionNumber + 1)!
213 let peer2ContextID = "asdf"
215 // Assist the other client here: it'll likely already have this built-in
216 self.fakeCuttlefishServer.policyOverlay.append(newPolicy)
218 let fetchExpectation = self.expectation(description: "fetch callback occurs")
219 self.tphClient.fetchPolicyDocuments(withContainer: OTCKContainerName,
220 context: peer2ContextID,
221 versions: Set([newPolicy.version])) { _, error in
222 XCTAssertNil(error, "Should have no error")
223 fetchExpectation.fulfill()
225 self.wait(for: [fetchExpectation], timeout: 10)
227 // Remove the policy, now that peer2 has it
228 self.fakeCuttlefishServer.policyOverlay.removeAll()
230 let joiningContext = self.makeInitiatorContext(contextID: peer2ContextID, authKitAdapter: self.mockAuthKit2)
231 joiningContext.policyOverride = newPolicy.version
233 let serverJoinExpectation = self.expectation(description: "peer2 joins successfully")
234 self.fakeCuttlefishServer.joinListener = { joinRequest in
235 XCTAssertTrue(joinRequest.peer.hasStableInfoAndSig, "Joining peer should have a stable info")
236 let newStableInfo = joinRequest.peer.stableInfoAndSig.stableInfo()
238 XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Frozen policy version in new identity should match frozen policy version")
239 XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicy.version, "Flexible policy version in new identity should match new policy version (as provided by new peer)")
241 serverJoinExpectation.fulfill()
245 _ = self.assertJoinViaEscrowRecovery(joiningContext: joiningContext, sponsor: self.cuttlefishContext)
246 self.wait(for: [serverJoinExpectation], timeout: 10)
248 // Then, after the remote peer joins, the original peer should ignore it entirely: peer1 has no idea what this new policy is about
249 // That means it won't update its trust in response to the join
250 self.fakeCuttlefishServer.updateListener = { request in
251 XCTFail("Expected no updateTrust after peer1 joins")
252 XCTAssertEqual(peer1ID, request.peerID, "updateTrust request should be for peer 1")
254 * But, if it did update its trust, here's what we would expect:
255 let newDynamicInfo = request.dynamicInfoAndSig.dynamicInfo()
256 XCTAssertFalse(newDynamicInfo.includedPeerIDs.contains(peer2ID), "Peer1 should not trust peer2")
257 XCTAssertFalse(newDynamicInfo.excludedPeerIDs.contains(peer2ID), "Peer1 should not distrust peer2")
259 let newStableInfo = request.stableInfoAndSig.stableInfo()
261 XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version")
262 XCTAssertEqual(newStableInfo.flexiblePolicyVersion, prevailingPolicyVersion, "Prevailing policy version in peer should match current prevailing policy version")
264 updateTrustExpectation.fulfill()
269 self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext)
270 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
273 func createOctagonAndCKKSUsingFuturePolicy() throws -> (TPPolicyDocument, CKRecordZone.ID) {
274 // We want to set up a world with a peer, in Octagon, with TLKs for zones that don't even exist in our current policy.
275 // First, make a new policy.
276 let currentPolicyOptional = builtInPolicyDocuments().filter { $0.version.versionNumber == prevailingPolicyVersion.versionNumber }.first
277 XCTAssertNotNil(currentPolicyOptional, "Should have one current policy")
278 let currentPolicyDocument = currentPolicyOptional!
280 let futureViewName = "FutureView"
281 let futureViewMapping = TPPBPolicyKeyViewMapping()!
283 let futureViewZoneID = CKRecordZone.ID(zoneName: futureViewName)
284 self.ckksZones.add(futureViewZoneID)
285 self.injectedManager!.setSyncingViewsAllowList(Set((self.intendedCKKSZones.union([futureViewZoneID])).map { $0.zoneName }))
287 self.zones![futureViewZoneID] = FakeCKZone(zone: futureViewZoneID)
289 XCTAssertFalse(currentPolicyDocument.categoriesByView.keys.contains(futureViewName), "Current policy should not include future view")
291 let newPolicyDocument = try TPPolicyDocument(internalVersion: currentPolicyDocument.version.versionNumber + 1,
292 modelToCategory: currentPolicyDocument.modelToCategory,
293 categoriesByView: currentPolicyDocument.categoriesByView.merging([futureViewName: Set(["watch", "full", "tv"])]) { _, new in new },
294 introducersByCategory: currentPolicyDocument.introducersByCategory,
296 keyViewMapping: [futureViewMapping] + currentPolicyDocument.keyViewMapping,
299 self.fakeCuttlefishServer.policyOverlay.append(newPolicyDocument)
301 return (newPolicyDocument, futureViewZoneID)
304 func testRestoreBottledPeerUsingFuturePolicy() throws {
305 let (newPolicyDocument, futureViewZoneID) = try self.createOctagonAndCKKSUsingFuturePolicy()
307 let futurePeerContext = self.makeInitiatorContext(contextID: "futurePeer")
308 futurePeerContext.policyOverride = newPolicyDocument.version
310 self.startCKAccountStatusMock()
311 let futurePeerID = self.assertResetAndBecomeTrusted(context: futurePeerContext)
313 self.putFakeKeyHierarchiesInCloudKit()
314 try self.putSelfTLKSharesInCloudKit(context: futurePeerContext)
315 XCTAssertTrue(try self.tlkShareInCloudKit(receiverPeerID: futurePeerID, senderPeerID: futurePeerID, zoneID: futureViewZoneID))
317 // Now, our peer (with no inbuilt knowledge of newPolicyDocument) joins via escrow recovery.
318 // It should be able to recover the FutureView TLK
319 self.assertAllCKKSViewsUpload(tlkShares: 1)
321 let serverJoinExpectation = self.expectation(description: "peer1 joins successfully")
322 self.fakeCuttlefishServer.joinListener = { joinRequest in
323 XCTAssertTrue(joinRequest.peer.hasStableInfoAndSig, "Joining peer should have a stable info")
324 let newStableInfo = joinRequest.peer.stableInfoAndSig.stableInfo()
326 XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version")
327 XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicyDocument.version, "Prevailing policy version in peer should match new policy version")
329 serverJoinExpectation.fulfill()
333 let peerID = self.assertJoinViaEscrowRecovery(joiningContext: self.cuttlefishContext, sponsor: futurePeerContext)
334 self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
335 XCTAssertEqual(self.injectedManager!.policy?.version, newPolicyDocument.version, "CKKS should be configured with new policy")
336 self.verifyDatabaseMocks()
338 self.wait(for: [serverJoinExpectation], timeout: 10)
340 // And the joined peer should have recovered the TLK, and uploaded itself a share
341 XCTAssertTrue(try self.tlkShareInCloudKit(receiverPeerID: peerID, senderPeerID: peerID, zoneID: futureViewZoneID))
344 func testPairingJoinUsingFuturePolicy() throws {
345 let (newPolicyDocument, futureViewZoneID) = try self.createOctagonAndCKKSUsingFuturePolicy()
347 let futurePeerContext = self.makeInitiatorContext(contextID: "futurePeer")
348 futurePeerContext.policyOverride = newPolicyDocument.version
350 self.startCKAccountStatusMock()
351 let futurePeerID = self.assertResetAndBecomeTrusted(context: futurePeerContext)
353 self.putFakeKeyHierarchiesInCloudKit()
354 try self.putSelfTLKSharesInCloudKit(context: futurePeerContext)
355 XCTAssertTrue(try self.tlkShareInCloudKit(receiverPeerID: futurePeerID, senderPeerID: futurePeerID, zoneID: futureViewZoneID))
357 // Now, our peer (with no inbuilt knowledge of newPolicyDocument) joins via pairing
358 // It should be able to recover the FutureView TLK
359 self.assertAllCKKSViewsUpload(tlkShares: 1)
361 let serverJoinExpectation = self.expectation(description: "peer1 joins successfully")
362 self.fakeCuttlefishServer.joinListener = { joinRequest in
363 XCTAssertTrue(joinRequest.peer.hasStableInfoAndSig, "Joining peer should have a stable info")
364 let newStableInfo = joinRequest.peer.stableInfoAndSig.stableInfo()
366 XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version")
367 XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicyDocument.version, "Prevailing policy version in peer should match new policy version")
369 serverJoinExpectation.fulfill()
373 let peerID = self.assertJoinViaProximitySetup(joiningContext: self.cuttlefishContext, sponsor: futurePeerContext)
375 // And then fake like the other peer uploaded TLKShares after the join succeeded (it would normally happen during, but that's okay)
376 try self.putAllTLKSharesInCloudKit(to: self.cuttlefishContext, from: futurePeerContext)
377 self.sendAllCKKSViewsZoneChanged()
379 self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
380 XCTAssertEqual(self.injectedManager!.policy?.version, newPolicyDocument.version, "CKKS should be configured with new policy")
381 self.verifyDatabaseMocks()
383 self.wait(for: [serverJoinExpectation], timeout: 10)
385 // And the joined peer should have recovered the TLK, and uploaded itself a share
386 XCTAssertTrue(try self.tlkShareInCloudKit(receiverPeerID: peerID, senderPeerID: peerID, zoneID: futureViewZoneID))
389 func testRecoveryKeyJoinUsingFuturePolicy() throws {
390 let (newPolicyDocument, futureViewZoneID) = try self.createOctagonAndCKKSUsingFuturePolicy()
392 let futurePeerContext = self.makeInitiatorContext(contextID: "futurePeer")
393 futurePeerContext.policyOverride = newPolicyDocument.version
395 self.startCKAccountStatusMock()
396 let futurePeerID = self.assertResetAndBecomeTrusted(context: futurePeerContext)
398 self.putFakeKeyHierarchiesInCloudKit()
399 try self.putSelfTLKSharesInCloudKit(context: futurePeerContext)
400 XCTAssertTrue(try self.tlkShareInCloudKit(receiverPeerID: futurePeerID, senderPeerID: futurePeerID, zoneID: futureViewZoneID))
402 // Create the recovery key
403 let recoveryKey = SecPasswordGenerate(SecPasswordType(kSecPasswordTypeiCloudRecoveryKey), nil, nil)! as String
404 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
406 self.manager.setSOSEnabledForPlatformFlag(true)
408 let createRecoveryExpectation = self.expectation(description: "createRecoveryExpectation returns")
409 self.manager.createRecoveryKey(OTCKContainerName, contextID: futurePeerContext.contextID, recoveryKey: recoveryKey) { error in
410 XCTAssertNil(error, "error should be nil")
411 createRecoveryExpectation.fulfill()
413 self.wait(for: [createRecoveryExpectation], timeout: 10)
415 // Setting the RK will make TLKShares to the RK, so help that out too
416 try self.putRecoveryKeyTLKSharesInCloudKit(recoveryKey: recoveryKey, salt: self.mockAuthKit.altDSID!)
418 // Now, our peer (with no inbuilt knowledge of newPolicyDocument) joins via RecoveryKey
419 let serverJoinExpectation = self.expectation(description: "peer1 joins successfully")
420 self.fakeCuttlefishServer.joinListener = { joinRequest in
421 XCTAssertTrue(joinRequest.peer.hasStableInfoAndSig, "Joining peer should have a stable info")
422 let newStableInfo = joinRequest.peer.stableInfoAndSig.stableInfo()
424 XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version")
425 XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicyDocument.version, "Prevailing policy version in peer should match new policy version")
427 serverJoinExpectation.fulfill()
431 // It should recover and upload the FutureView TLK
432 self.assertAllCKKSViewsUpload(tlkShares: 1)
434 self.cuttlefishContext.startOctagonStateMachine()
435 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
437 let joinWithRecoveryKeyExpectation = self.expectation(description: "joinWithRecoveryKey callback occurs")
438 self.cuttlefishContext.join(withRecoveryKey: recoveryKey) { error in
439 XCTAssertNil(error, "error should be nil")
440 joinWithRecoveryKeyExpectation.fulfill()
442 self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10)
444 self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
445 XCTAssertEqual(self.injectedManager!.policy?.version, newPolicyDocument.version, "CKKS should be configured with new policy")
446 self.verifyDatabaseMocks()
448 self.wait(for: [serverJoinExpectation], timeout: 10)
451 func testPreapprovedJoinUsingFuturePolicy() throws {
452 let peer2mockSOS = CKKSMockSOSPresentAdapter(selfPeer: self.createSOSPeer(peerID: "peer2ID"), trustedPeers: self.mockSOSAdapter.allPeers(), essential: false)
453 print(peer2mockSOS.allPeers())
454 self.mockSOSAdapter.trustedPeers.add(peer2mockSOS.selfPeer)
456 let futurePeerContext = self.manager.context(forContainerName: OTCKContainerName,
457 contextID: "futurePeer",
458 sosAdapter: peer2mockSOS,
459 authKitAdapter: self.mockAuthKit2,
460 lockStateTracker: self.lockStateTracker,
461 accountStateTracker: self.accountStateTracker,
462 deviceInformationAdapter: OTMockDeviceInfoAdapter(modelID: "iPhone9,1", deviceName: "test-SOS-iphone", serialNumber: "456", osVersion: "iOS (fake version)"))
464 let (newPolicyDocument, _) = try self.createOctagonAndCKKSUsingFuturePolicy()
465 futurePeerContext.policyOverride = newPolicyDocument.version
467 let serverEstablishExpectation = self.expectation(description: "futurePeer establishes successfully")
468 self.fakeCuttlefishServer.establishListener = { establishRequest in
469 XCTAssertTrue(establishRequest.peer.hasStableInfoAndSig, "Establishing peer should have a stable info")
470 let newStableInfo = establishRequest.peer.stableInfoAndSig.stableInfo()
472 XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version")
473 XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicyDocument.version, "Prevailing policy version in peer should match new policy version")
474 serverEstablishExpectation.fulfill()
478 // Setup is complete. Join using a new peer
479 self.startCKAccountStatusMock()
480 futurePeerContext.startOctagonStateMachine()
481 self.assertEnters(context: futurePeerContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
482 self.wait(for: [serverEstablishExpectation], timeout: 10)
484 self.putFakeKeyHierarchiesInCloudKit()
485 try self.putSelfTLKSharesInCloudKit(context: futurePeerContext)
487 // Now, the default peer joins via SOS preapproval
488 let serverJoinExpectation = self.expectation(description: "peer1 joins successfully")
489 self.fakeCuttlefishServer.joinListener = { joinRequest in
490 XCTAssertTrue(joinRequest.peer.hasStableInfoAndSig, "Joining peer should have a stable info")
491 let newStableInfo = joinRequest.peer.stableInfoAndSig.stableInfo()
493 XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version")
494 XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicyDocument.version, "Prevailing policy version in peer should match new policy version")
495 serverJoinExpectation.fulfill()
499 self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCInCircle)
500 self.cuttlefishContext.startOctagonStateMachine()
501 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
502 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
503 self.wait(for: [serverJoinExpectation], timeout: 10)
505 // But, since we're not mocking the remote peer sharing the TLKs, ckks should get stuck
506 self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLK, within: 10 * NSEC_PER_SEC)
507 XCTAssertEqual(self.injectedManager!.policy?.version, newPolicyDocument.version, "CKKS should be configured with new policy")
508 self.verifyDatabaseMocks()
511 func testRespondToFuturePoliciesInPeerUpdates() throws {
512 self.startCKAccountStatusMock()
513 self.assertResetAndBecomeTrustedInDefaultContext()
515 // Now, another peer comes along and joins via BP recovery, using a new policy
516 let (newPolicyDocument, _) = try self.createOctagonAndCKKSUsingFuturePolicy()
518 let futurePeerContext = self.makeInitiatorContext(contextID: "futurePeer")
519 futurePeerContext.policyOverride = newPolicyDocument.version
521 let serverJoinExpectation = self.expectation(description: "futurePeer joins successfully")
522 self.fakeCuttlefishServer.joinListener = { joinRequest in
523 XCTAssertTrue(joinRequest.peer.hasStableInfoAndSig, "Joining peer should have a stable info")
524 let newStableInfo = joinRequest.peer.stableInfoAndSig.stableInfo()
526 XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version")
527 XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicyDocument.version, "Prevailing policy version in peer should match new policy version")
528 serverJoinExpectation.fulfill()
532 let peer2ID = self.assertJoinViaEscrowRecovery(joiningContext: futurePeerContext, sponsor: self.cuttlefishContext)
533 self.wait(for: [serverJoinExpectation], timeout: 10)
535 // Now, tell our first peer about the new changes. It should trust the new peer, and update its policy
536 let updateTrustExpectation = self.expectation(description: "updateTrustExpectation successfully")
537 self.fakeCuttlefishServer.updateListener = { request in
538 let newStableInfo = request.stableInfoAndSig.stableInfo()
540 XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version")
541 XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicyDocument.version, "Prevailing policy version in peer should match new policy version")
543 let newDynamicInfo = request.dynamicInfoAndSig.dynamicInfo()
544 XCTAssert(newDynamicInfo.includedPeerIDs.contains(peer2ID), "Peer1 should trust peer2")
546 updateTrustExpectation.fulfill()
550 self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext)
551 self.wait(for: [updateTrustExpectation], timeout: 10)