OctagonTests+ForwardCompatibility.swift   [plain text]


#if OCTAGON

import Foundation

class OctagonForwardCompatibilityTests: OctagonTestsBase {
    func testApprovePeerWithNewPolicy() throws {
        self.startCKAccountStatusMock()
        let peer1ID = self.assertResetAndBecomeTrustedInDefaultContext()

        // Now, we'll approve a new peer with a new policy! First, make that new policy.
        let currentPolicyOptional = builtInPolicyDocuments().filter { $0.version.versionNumber == prevailingPolicyVersion.versionNumber }.first
        XCTAssertNotNil(currentPolicyOptional, "Should have one current policy")
        let currentPolicy = currentPolicyOptional!

        let newPolicy = currentPolicy.clone(withVersionNumber: currentPolicy.version.versionNumber + 1)!
        self.fakeCuttlefishServer.policyOverlay.append(newPolicy)

        let peer2ContextID = "asdf"

        // Assist the other client here: it'll likely already have the policy built-in
        let fetchExpectation = self.expectation(description: "fetch callback occurs")
        self.tphClient.fetchPolicyDocuments(withContainer: OTCKContainerName,
                                            context: peer2ContextID,
                                            versions: Set([newPolicy.version])) { _, error in
                                                XCTAssertNil(error, "Should have no error")
                                                fetchExpectation.fulfill()
        }
        self.wait(for: [fetchExpectation], timeout: 10)

        var peer2ID: String = "not initialized"

        let prepareExpectation = self.expectation(description: "prepare callback occurs")
        let vouchExpectation = self.expectation(description: "vouch callback occurs")
        let joinExpectation = self.expectation(description: "join callback occurs")
        let serverJoinExpectation = self.expectation(description: "joinWithVoucher is called")

        self.tphClient.prepare(withContainer: OTCKContainerName,
                               context: peer2ContextID,
                               epoch: 1,
                               machineID: self.mockAuthKit2.currentMachineID,
                               bottleSalt: self.mockAuthKit2.altDSID!,
                               bottleID: "why-is-this-nonnil",
                               modelID: self.mockDeviceInfo.modelID(),
                               deviceName: "new-policy-peer",
                               serialNumber: "1234",
                               osVersion: "something",
                               policyVersion: newPolicy.version,
                               policySecrets: nil,
                               signingPrivKeyPersistentRef: nil,
                               encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, _, _, error in
                                XCTAssertNil(error, "Should be no error preparing the second peer")
                                XCTAssertNotNil(peerID, "Should have a peerID")
                                peer2ID = peerID!

                                XCTAssertNotNil(stableInfo, "Should have a stable info")
                                XCTAssertNotNil(stableInfoSig, "Should have a stable info signature")

                                let newStableInfo = TPPeerStableInfo(data: stableInfo!, sig: stableInfoSig!)
                                XCTAssertNotNil(newStableInfo, "should be able to make a stableInfo info from protobuf")

                                XCTAssertEqual(newStableInfo?.frozenPolicyVersion, frozenPolicyVersion, "Frozen policy version in new identity should match frozen policy version")
                                XCTAssertEqual(newStableInfo?.flexiblePolicyVersion, newPolicy.version, "Flexible policy version in new identity should match new policy version")

                                self.tphClient.vouch(withContainer: self.cuttlefishContext.containerName,
                                                     context: self.cuttlefishContext.contextID,
                                                     peerID: peerID!,
                                                     permanentInfo: permanentInfo!,
                                                     permanentInfoSig: permanentInfoSig!,
                                                     stableInfo: stableInfo!,
                                                     stableInfoSig: stableInfoSig!,
                                                     ckksKeys: []) { voucher, voucherSig, error in
                                                        XCTAssertNil(error, "Should be no error vouching")
                                                        XCTAssertNotNil(voucher, "Should have a voucher")
                                                        XCTAssertNotNil(voucherSig, "Should have a voucher signature")

                                                        self.fakeCuttlefishServer.joinListener = { joinRequest in
                                                            XCTAssertEqual(peer2ID, joinRequest.peer.peerID, "joinWithVoucher request should be for peer 2")
                                                            XCTAssertTrue(joinRequest.peer.hasStableInfoAndSig, "Joining peer should have a stable info")
                                                            let newStableInfo = joinRequest.peer.stableInfoAndSig.stableInfo()

                                                            XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Frozen policy version in new identity should match frozen policy version")
                                                            XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicy.version, "Flexible policy version in new identity should match new policy version (as provided by new peer)")

                                                            serverJoinExpectation.fulfill()
                                                            return nil
                                                        }

                                                        self.tphClient.join(withContainer: OTCKContainerName,
                                                                            context: peer2ContextID,
                                                                            voucherData: voucher!,
                                                                            voucherSig: voucherSig!,
                                                                            ckksKeys: [],
                                                                            tlkShares: [],
                                                                            preapprovedKeys: []) { peerID, _, _, _, error in
                                                                                XCTAssertNil(error, "Should be no error joining")
                                                                                XCTAssertNotNil(peerID, "Should have a peerID")
                                                                                joinExpectation.fulfill()
                                                        }
                                                        vouchExpectation.fulfill()
                                }
                                prepareExpectation.fulfill()
        }
        self.wait(for: [prepareExpectation, vouchExpectation, joinExpectation, serverJoinExpectation], timeout: 10)

        // 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
        let updateTrustExpectation = self.expectation(description: "updateTrust")
        self.fakeCuttlefishServer.updateListener = { request in
            XCTAssertEqual(peer1ID, request.peerID, "updateTrust request should be for peer 1")
            let newDynamicInfo = request.dynamicInfoAndSig.dynamicInfo()
            XCTAssert(newDynamicInfo.includedPeerIDs.contains(peer2ID), "Peer1 should trust peer2")

            let newStableInfo = request.stableInfoAndSig.stableInfo()

            XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version")
            XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicy.version, "Prevailing policy version in peer should match new policy version")

            updateTrustExpectation.fulfill()
            return nil
        }

        self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext)
        self.wait(for: [updateTrustExpectation], timeout: 10)
        self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
    }

    func testRejectVouchingForPeerWithUnknownNewPolicy() throws {
        self.startCKAccountStatusMock()
        _ = self.assertResetAndBecomeTrustedInDefaultContext()

        // Now, a new peer joins with a policy we can't fetch
        let currentPolicyOptional = builtInPolicyDocuments().filter { $0.version.versionNumber == prevailingPolicyVersion.versionNumber }.first
        XCTAssertNotNil(currentPolicyOptional, "Should have one current policy")
        let currentPolicy = currentPolicyOptional!

        let newPolicy = currentPolicy.clone(withVersionNumber: currentPolicy.version.versionNumber + 1)!

        let peer2ContextID = "asdf"

        // Assist the other client here: it'll likely already have this built-in
        self.fakeCuttlefishServer.policyOverlay.append(newPolicy)

        let fetchExpectation = self.expectation(description: "fetch callback occurs")
        self.tphClient.fetchPolicyDocuments(withContainer: OTCKContainerName,
                                            context: peer2ContextID,
                                            versions: Set([newPolicy.version])) { _, error in
                                                XCTAssertNil(error, "Should have no error")
                                                fetchExpectation.fulfill()
        }
        self.wait(for: [fetchExpectation], timeout: 10)

        // Remove the policy, now that peer2 has it
        self.fakeCuttlefishServer.policyOverlay.removeAll()

        let prepareExpectation = self.expectation(description: "prepare callback occurs")
        let vouchExpectation = self.expectation(description: "vouch callback occurs")

        self.tphClient.prepare(withContainer: OTCKContainerName,
                               context: peer2ContextID,
                               epoch: 1,
                               machineID: self.mockAuthKit2.currentMachineID,
                               bottleSalt: self.mockAuthKit2.altDSID!,
                               bottleID: "why-is-this-nonnil",
                               modelID: self.mockDeviceInfo.modelID(),
                               deviceName: "new-policy-peer",
                               serialNumber: "1234",
                               osVersion: "something",
                               policyVersion: newPolicy.version,
                               policySecrets: nil,
                               signingPrivKeyPersistentRef: nil,
                               encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, _, _, error in
                                XCTAssertNil(error, "Should be no error preparing the second peer")
                                XCTAssertNotNil(peerID, "Should have a peerID")

                                XCTAssertNotNil(stableInfo, "Should have a stable info")
                                XCTAssertNotNil(stableInfoSig, "Should have a stable info signature")

                                let newStableInfo = TPPeerStableInfo(data: stableInfo!, sig: stableInfoSig!)
                                XCTAssertNotNil(newStableInfo, "should be able to make a stableInfo info from protobuf")

                                XCTAssertEqual(newStableInfo?.frozenPolicyVersion, frozenPolicyVersion, "Frozen policy version in new identity should match frozen policy version")
                                XCTAssertEqual(newStableInfo?.flexiblePolicyVersion, newPolicy.version, "Flexible policy version in new identity should match new policy version")

                                self.tphClient.vouch(withContainer: self.cuttlefishContext.containerName,
                                                     context: self.cuttlefishContext.contextID,
                                                     peerID: peerID!,
                                                     permanentInfo: permanentInfo!,
                                                     permanentInfoSig: permanentInfoSig!,
                                                     stableInfo: stableInfo!,
                                                     stableInfoSig: stableInfoSig!,
                                                     ckksKeys: []) { voucher, voucherSig, error in
                                                        XCTAssertNotNil(error, "should be an error vouching for a peer with an unknown policy")
                                                        XCTAssertNil(voucher, "Should have no voucher")
                                                        XCTAssertNil(voucherSig, "Should have no voucher signature")

                                                        vouchExpectation.fulfill()
                                }
                                prepareExpectation.fulfill()
        }
        self.wait(for: [prepareExpectation, vouchExpectation], timeout: 10)
    }

    func testIgnoreAlreadyJoinedPeerWithUnknownNewPolicy() throws {
        self.startCKAccountStatusMock()
        let peer1ID = self.assertResetAndBecomeTrustedInDefaultContext()

        // Now, a new peer joins with a policy we can't fetch
        let currentPolicyOptional = builtInPolicyDocuments().filter { $0.version.versionNumber == prevailingPolicyVersion.versionNumber }.first
        XCTAssertNotNil(currentPolicyOptional, "Should have one current policy")
        let currentPolicy = currentPolicyOptional!

        let newPolicy = currentPolicy.clone(withVersionNumber: currentPolicy.version.versionNumber + 1)!

        let peer2ContextID = "asdf"

        // Assist the other client here: it'll likely already have this built-in
        self.fakeCuttlefishServer.policyOverlay.append(newPolicy)

        let fetchExpectation = self.expectation(description: "fetch callback occurs")
        self.tphClient.fetchPolicyDocuments(withContainer: OTCKContainerName,
                                            context: peer2ContextID,
                                            versions: Set([newPolicy.version])) { _, error in
                                                XCTAssertNil(error, "Should have no error")
                                                fetchExpectation.fulfill()
        }
        self.wait(for: [fetchExpectation], timeout: 10)

        // Remove the policy, now that peer2 has it
        self.fakeCuttlefishServer.policyOverlay.removeAll()

        let joiningContext = self.makeInitiatorContext(contextID: peer2ContextID, authKitAdapter: self.mockAuthKit2)
        joiningContext.policyOverride = newPolicy.version

        let serverJoinExpectation = self.expectation(description: "peer2 joins successfully")
        self.fakeCuttlefishServer.joinListener = { joinRequest in
            XCTAssertTrue(joinRequest.peer.hasStableInfoAndSig, "Joining peer should have a stable info")
            let newStableInfo = joinRequest.peer.stableInfoAndSig.stableInfo()

            XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Frozen policy version in new identity should match frozen policy version")
            XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicy.version, "Flexible policy version in new identity should match new policy version (as provided by new peer)")

            serverJoinExpectation.fulfill()
            return nil
        }

        _ = self.assertJoinViaEscrowRecovery(joiningContext: joiningContext, sponsor: self.cuttlefishContext)
        self.wait(for: [serverJoinExpectation], timeout: 10)

        // Then, after the remote peer joins, the original peer should ignore it entirely: peer1 has no idea what this new policy is about
        // That means it won't update its trust in response to the join
        self.fakeCuttlefishServer.updateListener = { request in
            XCTFail("Expected no updateTrust after peer1 joins")
            XCTAssertEqual(peer1ID, request.peerID, "updateTrust request should be for peer 1")
            /*
             * But, if it did update its trust, here's what we would expect:
            let newDynamicInfo = request.dynamicInfoAndSig.dynamicInfo()
            XCTAssertFalse(newDynamicInfo.includedPeerIDs.contains(peer2ID), "Peer1 should not trust peer2")
            XCTAssertFalse(newDynamicInfo.excludedPeerIDs.contains(peer2ID), "Peer1 should not distrust peer2")

            let newStableInfo = request.stableInfoAndSig.stableInfo()

            XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version")
            XCTAssertEqual(newStableInfo.flexiblePolicyVersion, prevailingPolicyVersion, "Prevailing policy version in peer should match current prevailing policy version")

            updateTrustExpectation.fulfill()
             */
            return nil
        }

        self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext)
        self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
    }

    func createOctagonAndCKKSUsingFuturePolicy() throws -> (TPPolicyDocument, CKRecordZone.ID) {
        // 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.
        // First, make a new policy.
        let currentPolicyOptional = builtInPolicyDocuments().filter { $0.version.versionNumber == prevailingPolicyVersion.versionNumber }.first
        XCTAssertNotNil(currentPolicyOptional, "Should have one current policy")
        let currentPolicyDocument = currentPolicyOptional!

        let futureViewName = "FutureView"
        let futureViewMapping = TPPBPolicyKeyViewMapping()!

        let futureViewZoneID = CKRecordZone.ID(zoneName: futureViewName)
        self.ckksZones.add(futureViewZoneID)
        self.injectedManager!.setSyncingViewsAllowList(Set((self.intendedCKKSZones.union([futureViewZoneID])).map { $0.zoneName }))

        self.zones![futureViewZoneID] = FakeCKZone(zone: futureViewZoneID)

        XCTAssertFalse(currentPolicyDocument.categoriesByView.keys.contains(futureViewName), "Current policy should not include future view")

        let newPolicyDocument = try TPPolicyDocument(internalVersion: currentPolicyDocument.version.versionNumber + 1,
            modelToCategory: currentPolicyDocument.modelToCategory,
            categoriesByView: currentPolicyDocument.categoriesByView.merging([futureViewName: Set(["watch", "full", "tv"])]) { _, new in new },
            introducersByCategory: currentPolicyDocument.introducersByCategory,
            redactions: [:],
            keyViewMapping: [futureViewMapping] + currentPolicyDocument.keyViewMapping,
            hashAlgo: .SHA256)

        self.fakeCuttlefishServer.policyOverlay.append(newPolicyDocument)

        return (newPolicyDocument, futureViewZoneID)
    }

    func testRestoreBottledPeerUsingFuturePolicy() throws {
        let (newPolicyDocument, futureViewZoneID) = try self.createOctagonAndCKKSUsingFuturePolicy()

        let futurePeerContext = self.makeInitiatorContext(contextID: "futurePeer")
        futurePeerContext.policyOverride = newPolicyDocument.version

        self.startCKAccountStatusMock()
        let futurePeerID = self.assertResetAndBecomeTrusted(context: futurePeerContext)

        self.putFakeKeyHierarchiesInCloudKit()
        try self.putSelfTLKSharesInCloudKit(context: futurePeerContext)
        XCTAssertTrue(try self.tlkShareInCloudKit(receiverPeerID: futurePeerID, senderPeerID: futurePeerID, zoneID: futureViewZoneID))

        // Now, our peer (with no inbuilt knowledge of newPolicyDocument) joins via escrow recovery.
        // It should be able to recover the FutureView TLK
        self.assertAllCKKSViewsUpload(tlkShares: 1)

        let serverJoinExpectation = self.expectation(description: "peer1 joins successfully")
        self.fakeCuttlefishServer.joinListener = { joinRequest in
            XCTAssertTrue(joinRequest.peer.hasStableInfoAndSig, "Joining peer should have a stable info")
            let newStableInfo = joinRequest.peer.stableInfoAndSig.stableInfo()

            XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version")
            XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicyDocument.version, "Prevailing policy version in peer should match new policy version")

            serverJoinExpectation.fulfill()
            return nil
        }

        let peerID = self.assertJoinViaEscrowRecovery(joiningContext: self.cuttlefishContext, sponsor: futurePeerContext)
        self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
        XCTAssertEqual(self.injectedManager!.policy?.version, newPolicyDocument.version, "CKKS should be configured with new policy")
        self.verifyDatabaseMocks()

        self.wait(for: [serverJoinExpectation], timeout: 10)

        // And the joined peer should have recovered the TLK, and uploaded itself a share
        XCTAssertTrue(try self.tlkShareInCloudKit(receiverPeerID: peerID, senderPeerID: peerID, zoneID: futureViewZoneID))
    }

    func testPairingJoinUsingFuturePolicy() throws {
        let (newPolicyDocument, futureViewZoneID) = try self.createOctagonAndCKKSUsingFuturePolicy()

        let futurePeerContext = self.makeInitiatorContext(contextID: "futurePeer")
        futurePeerContext.policyOverride = newPolicyDocument.version

        self.startCKAccountStatusMock()
        let futurePeerID = self.assertResetAndBecomeTrusted(context: futurePeerContext)

        self.putFakeKeyHierarchiesInCloudKit()
        try self.putSelfTLKSharesInCloudKit(context: futurePeerContext)
        XCTAssertTrue(try self.tlkShareInCloudKit(receiverPeerID: futurePeerID, senderPeerID: futurePeerID, zoneID: futureViewZoneID))

        // Now, our peer (with no inbuilt knowledge of newPolicyDocument) joins via pairing
        // It should be able to recover the FutureView TLK
        self.assertAllCKKSViewsUpload(tlkShares: 1)

        let serverJoinExpectation = self.expectation(description: "peer1 joins successfully")
        self.fakeCuttlefishServer.joinListener = { joinRequest in
            XCTAssertTrue(joinRequest.peer.hasStableInfoAndSig, "Joining peer should have a stable info")
            let newStableInfo = joinRequest.peer.stableInfoAndSig.stableInfo()

            XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version")
            XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicyDocument.version, "Prevailing policy version in peer should match new policy version")

            serverJoinExpectation.fulfill()
            return nil
        }

        let peerID = self.assertJoinViaProximitySetup(joiningContext: self.cuttlefishContext, sponsor: futurePeerContext)

        // And then fake like the other peer uploaded TLKShares after the join succeeded (it would normally happen during, but that's okay)
        try self.putAllTLKSharesInCloudKit(to: self.cuttlefishContext, from: futurePeerContext)
        self.sendAllCKKSViewsZoneChanged()

        self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
        XCTAssertEqual(self.injectedManager!.policy?.version, newPolicyDocument.version, "CKKS should be configured with new policy")
        self.verifyDatabaseMocks()

        self.wait(for: [serverJoinExpectation], timeout: 10)

        // And the joined peer should have recovered the TLK, and uploaded itself a share
        XCTAssertTrue(try self.tlkShareInCloudKit(receiverPeerID: peerID, senderPeerID: peerID, zoneID: futureViewZoneID))
    }

    func testRecoveryKeyJoinUsingFuturePolicy() throws {
        let (newPolicyDocument, futureViewZoneID) = try self.createOctagonAndCKKSUsingFuturePolicy()

        let futurePeerContext = self.makeInitiatorContext(contextID: "futurePeer")
        futurePeerContext.policyOverride = newPolicyDocument.version

        self.startCKAccountStatusMock()
        let futurePeerID = self.assertResetAndBecomeTrusted(context: futurePeerContext)

        self.putFakeKeyHierarchiesInCloudKit()
        try self.putSelfTLKSharesInCloudKit(context: futurePeerContext)
        XCTAssertTrue(try self.tlkShareInCloudKit(receiverPeerID: futurePeerID, senderPeerID: futurePeerID, zoneID: futureViewZoneID))

        // Create the recovery key
        let recoveryKey = SecPasswordGenerate(SecPasswordType(kSecPasswordTypeiCloudRecoveryKey), nil, nil)! as String
        XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")

        self.manager.setSOSEnabledForPlatformFlag(true)

        let createRecoveryExpectation = self.expectation(description: "createRecoveryExpectation returns")
        self.manager.createRecoveryKey(OTCKContainerName, contextID: futurePeerContext.contextID, recoveryKey: recoveryKey) { error in
            XCTAssertNil(error, "error should be nil")
            createRecoveryExpectation.fulfill()
        }
        self.wait(for: [createRecoveryExpectation], timeout: 10)

        // Setting the RK will make TLKShares to the RK, so help that out too
        try self.putRecoveryKeyTLKSharesInCloudKit(recoveryKey: recoveryKey, salt: self.mockAuthKit.altDSID!)

        // Now, our peer (with no inbuilt knowledge of newPolicyDocument) joins via RecoveryKey
        let serverJoinExpectation = self.expectation(description: "peer1 joins successfully")
        self.fakeCuttlefishServer.joinListener = { joinRequest in
            XCTAssertTrue(joinRequest.peer.hasStableInfoAndSig, "Joining peer should have a stable info")
            let newStableInfo = joinRequest.peer.stableInfoAndSig.stableInfo()

            XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version")
            XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicyDocument.version, "Prevailing policy version in peer should match new policy version")

            serverJoinExpectation.fulfill()
            return nil
        }

        // It should recover and upload the FutureView TLK
        self.assertAllCKKSViewsUpload(tlkShares: 1)

        self.cuttlefishContext.startOctagonStateMachine()
        self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)

        let joinWithRecoveryKeyExpectation = self.expectation(description: "joinWithRecoveryKey callback occurs")
        self.cuttlefishContext.join(withRecoveryKey: recoveryKey) { error in
            XCTAssertNil(error, "error should be nil")
            joinWithRecoveryKeyExpectation.fulfill()
        }
        self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10)

        self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
        XCTAssertEqual(self.injectedManager!.policy?.version, newPolicyDocument.version, "CKKS should be configured with new policy")
        self.verifyDatabaseMocks()

        self.wait(for: [serverJoinExpectation], timeout: 10)
    }

    func testPreapprovedJoinUsingFuturePolicy() throws {
        let peer2mockSOS = CKKSMockSOSPresentAdapter(selfPeer: self.createSOSPeer(peerID: "peer2ID"), trustedPeers: self.mockSOSAdapter.allPeers(), essential: false)
        print(peer2mockSOS.allPeers())
        self.mockSOSAdapter.trustedPeers.add(peer2mockSOS.selfPeer)

        let futurePeerContext = self.manager.context(forContainerName: OTCKContainerName,
                                                     contextID: "futurePeer",
                                                     sosAdapter: peer2mockSOS,
                                                     authKitAdapter: self.mockAuthKit2,
                                                     lockStateTracker: self.lockStateTracker,
                                                     accountStateTracker: self.accountStateTracker,
                                                     deviceInformationAdapter: OTMockDeviceInfoAdapter(modelID: "iPhone9,1", deviceName: "test-SOS-iphone", serialNumber: "456", osVersion: "iOS (fake version)"))

        let (newPolicyDocument, _) = try self.createOctagonAndCKKSUsingFuturePolicy()
        futurePeerContext.policyOverride = newPolicyDocument.version

        let serverEstablishExpectation = self.expectation(description: "futurePeer establishes successfully")
        self.fakeCuttlefishServer.establishListener = { establishRequest in
            XCTAssertTrue(establishRequest.peer.hasStableInfoAndSig, "Establishing peer should have a stable info")
            let newStableInfo = establishRequest.peer.stableInfoAndSig.stableInfo()

            XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version")
            XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicyDocument.version, "Prevailing policy version in peer should match new policy version")
            serverEstablishExpectation.fulfill()
            return nil
        }

        // Setup is complete. Join using a new peer
        self.startCKAccountStatusMock()
        futurePeerContext.startOctagonStateMachine()
        self.assertEnters(context: futurePeerContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
        self.wait(for: [serverEstablishExpectation], timeout: 10)

        self.putFakeKeyHierarchiesInCloudKit()
        try self.putSelfTLKSharesInCloudKit(context: futurePeerContext)

        // Now, the default peer joins via SOS preapproval
        let serverJoinExpectation = self.expectation(description: "peer1 joins successfully")
        self.fakeCuttlefishServer.joinListener = { joinRequest in
            XCTAssertTrue(joinRequest.peer.hasStableInfoAndSig, "Joining peer should have a stable info")
            let newStableInfo = joinRequest.peer.stableInfoAndSig.stableInfo()

            XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version")
            XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicyDocument.version, "Prevailing policy version in peer should match new policy version")
            serverJoinExpectation.fulfill()
            return nil
        }

        self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCInCircle)
        self.cuttlefishContext.startOctagonStateMachine()
        self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
        self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
        self.wait(for: [serverJoinExpectation], timeout: 10)

        // But, since we're not mocking the remote peer sharing the TLKs, ckks should get stuck
        self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLK, within: 10 * NSEC_PER_SEC)
        XCTAssertEqual(self.injectedManager!.policy?.version, newPolicyDocument.version, "CKKS should be configured with new policy")
        self.verifyDatabaseMocks()
    }

    func testRespondToFuturePoliciesInPeerUpdates() throws {
        self.startCKAccountStatusMock()
        self.assertResetAndBecomeTrustedInDefaultContext()

        // Now, another peer comes along and joins via BP recovery, using a new policy
        let (newPolicyDocument, _) = try self.createOctagonAndCKKSUsingFuturePolicy()

        let futurePeerContext = self.makeInitiatorContext(contextID: "futurePeer")
        futurePeerContext.policyOverride = newPolicyDocument.version

        let serverJoinExpectation = self.expectation(description: "futurePeer joins successfully")
         self.fakeCuttlefishServer.joinListener = { joinRequest in
             XCTAssertTrue(joinRequest.peer.hasStableInfoAndSig, "Joining peer should have a stable info")
             let newStableInfo = joinRequest.peer.stableInfoAndSig.stableInfo()

             XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version")
             XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicyDocument.version, "Prevailing policy version in peer should match new policy version")
             serverJoinExpectation.fulfill()
             return nil
         }

        let peer2ID = self.assertJoinViaEscrowRecovery(joiningContext: futurePeerContext, sponsor: self.cuttlefishContext)
        self.wait(for: [serverJoinExpectation], timeout: 10)

        // Now, tell our first peer about the new changes. It should trust the new peer, and update its policy
        let updateTrustExpectation = self.expectation(description: "updateTrustExpectation successfully")
        self.fakeCuttlefishServer.updateListener = { request in
            let newStableInfo = request.stableInfoAndSig.stableInfo()

            XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version")
            XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicyDocument.version, "Prevailing policy version in peer should match new policy version")

            let newDynamicInfo = request.dynamicInfoAndSig.dynamicInfo()
            XCTAssert(newDynamicInfo.includedPeerIDs.contains(peer2ID), "Peer1 should trust peer2")

            updateTrustExpectation.fulfill()
            return nil
        }

        self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext)
        self.wait(for: [updateTrustExpectation], timeout: 10)
    }
}

#endif