#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