#if OCTAGON class OctagonDeviceListTests: OctagonTestsBase { func testSignInFailureBecauseUntrustedDevice() throws { // Check that we honor IdMS trusted device list that we got and reject device that // are not it self.startCKAccountStatusMock() // Must positively assert some device in list, so that the machine ID list isn't empty self.mockAuthKit.otherDevices.insert("some-machine-id") self.mockAuthKit.excludeDevices.insert(try! self.mockAuthKit.machineID()) self.cuttlefishContext.startOctagonStateMachine() self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let expectFail = self.expectation(description: "expect to fail") do { let clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated) XCTAssertNil(clique, "Clique should be nil") } catch { expectFail.fulfill() } self.wait(for: [expectFail], timeout: 10) // Now, we should be in 'untrusted' self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) } func testSignInWithIDMSBypass() throws { // Check that we can bypass IdMS trusted device list (needed for demo accounts) self.startCKAccountStatusMock() self.mockAuthKit.excludeDevices.formUnion(self.mockAuthKit.currentDeviceList()) XCTAssertTrue(self.mockAuthKit.currentDeviceList().isEmpty, "should have zero devices") self.cuttlefishContext.startOctagonStateMachine() self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) do { let clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated) XCTAssertNotNil(clique, "Clique should not be nil") } catch { XCTFail("Shouldn't have errored making new friends: \(error)") } // Now, we should be in 'ready' self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfTrusted(context: self.cuttlefishContext) self.assertConsidersSelfTrustedCachedAccountStatus(context: self.cuttlefishContext) // and all subCKKSes should enter ready... self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) self.verifyDatabaseMocks() self.assertSelfTLKSharesInCloudKit(context: self.cuttlefishContext) // And we haven't helpfully added anything to the MID list self.assertMIDList(context: self.cuttlefishContext, allowed: Set(), disallowed: Set(), unknown: Set()) } func testRemovePeerWhenRemovedFromDeviceList() throws { self.startCKAccountStatusMock() XCTAssert(self.mockAuthKit.currentDeviceList().contains(self.mockAuthKit2.currentMachineID), "AuthKit should already have device 2 on the list") let peer1ID = self.assertResetAndBecomeTrustedInDefaultContext() let joiningContext = self.makeInitiatorContext(contextID: "joiner", authKitAdapter: self.mockAuthKit2) let peer2ID = self.assertJoinViaEscrowRecovery(joiningContext: joiningContext, sponsor: self.cuttlefishContext) // Now, tell peer1 about the change. It will trust the peer and upload some TLK shares self.assertAllCKKSViewsUpload(tlkShares: 1) self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext) assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) self.verifyDatabaseMocks() XCTAssertTrue(self.fakeCuttlefishServer.assertCuttlefishState(FakeCuttlefishAssertion(peer: peer1ID, opinion: .trusts, target: peer2ID)), "peer 1 should trust peer 2 after update") // Then peer2 drops off the device list. Peer 1 should distrust peer2. let updateTrustExpectation = self.expectation(description: "updateTrust") self.fakeCuttlefishServer.updateListener = { request in XCTAssertTrue(request.hasDynamicInfoAndSig, "updateTrust request should have a dynamic info") let newDynamicInfo = TPPeerDynamicInfo(data: request.dynamicInfoAndSig.peerDynamicInfo, sig: request.dynamicInfoAndSig.sig) XCTAssertNotNil(newDynamicInfo, "should be able to make a dynamic info from protobuf") XCTAssertTrue(!(newDynamicInfo?.includedPeerIDs.contains(peer2ID) ?? true), "peer1 should no longer trust peer2") updateTrustExpectation.fulfill() return nil } self.mockAuthKit.removeAndSendNotification(machineID: try! self.mockAuthKit2.machineID()) self.wait(for: [updateTrustExpectation], timeout: 10) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) XCTAssertTrue(self.fakeCuttlefishServer.assertCuttlefishState(FakeCuttlefishAssertion(peer: peer1ID, opinion: .excludes, target: peer2ID)), "peer 1 should distrust peer 2 after update") } func testRemovePeerWhenRemovedFromDeviceListViaIncompleteNotification() throws { self.startCKAccountStatusMock() XCTAssert(self.mockAuthKit.currentDeviceList().contains(self.mockAuthKit2.currentMachineID), "AuthKit should already have device 2 on the list") let peer1ID = self.assertResetAndBecomeTrustedInDefaultContext() let joiningContext = self.makeInitiatorContext(contextID: "joiner", authKitAdapter: self.mockAuthKit2) let peer2ID = self.assertJoinViaEscrowRecovery(joiningContext: joiningContext, sponsor: self.cuttlefishContext) // Now, tell peer1 about the change. It will trust the peer and upload some TLK shares self.assertAllCKKSViewsUpload(tlkShares: 1) self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext) assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) self.verifyDatabaseMocks() XCTAssertTrue(self.fakeCuttlefishServer.assertCuttlefishState(FakeCuttlefishAssertion(peer: peer1ID, opinion: .trusts, target: peer2ID)), "peer 1 should trust peer 2 after update") // Then peer2 drops off the device list. Peer 1 should distrust peer2. let updateTrustExpectation = self.expectation(description: "updateTrust") self.fakeCuttlefishServer.updateListener = { request in XCTAssertTrue(request.hasDynamicInfoAndSig, "updateTrust request should have a dynamic info") let newDynamicInfo = TPPeerDynamicInfo(data: request.dynamicInfoAndSig.peerDynamicInfo, sig: request.dynamicInfoAndSig.sig) XCTAssertNotNil(newDynamicInfo, "should be able to make a dynamic info from protobuf") XCTAssertTrue(!(newDynamicInfo?.includedPeerIDs.contains(peer2ID) ?? true), "peer1 should no longer trust peer2") updateTrustExpectation.fulfill() return nil } self.mockAuthKit.excludeDevices.insert(try! self.mockAuthKit2.machineID()) XCTAssertFalse(self.mockAuthKit.currentDeviceList().contains(self.mockAuthKit2.currentMachineID), "AuthKit should not still have device 2 on the list") self.mockAuthKit.sendIncompleteNotification() self.wait(for: [updateTrustExpectation], timeout: 10) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) XCTAssertTrue(self.fakeCuttlefishServer.assertCuttlefishState(FakeCuttlefishAssertion(peer: peer1ID, opinion: .excludes, target: peer2ID)), "peer 1 should distrust peer 2 after update") } func testTrustPeerWhenMissingFromDeviceList() throws { self.startCKAccountStatusMock() self.mockAuthKit.otherDevices.removeAll() XCTAssertEqual(self.mockAuthKit.currentDeviceList(), Set([self.mockAuthKit.currentMachineID]), "AuthKit should have exactly one device on the list") XCTAssertFalse(self.mockAuthKit.currentDeviceList().contains(self.mockAuthKit2.currentMachineID), "AuthKit should not already have device 2 on the list") let peer1ID = self.assertResetAndBecomeTrustedInDefaultContext() let joiningContext = self.makeInitiatorContext(contextID: "joiner", authKitAdapter: self.mockAuthKit2) let peer2ID = self.assertJoinViaEscrowRecovery(joiningContext: joiningContext, sponsor: self.cuttlefishContext) // Now, tell peer1 about the change. It will trust the peer, despite it missing from the list, and upload some TLK shares self.assertAllCKKSViewsUpload(tlkShares: 1) self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext) self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) self.verifyDatabaseMocks() XCTAssertTrue(self.fakeCuttlefishServer.assertCuttlefishState(FakeCuttlefishAssertion(peer: peer1ID, opinion: .trusts, target: peer2ID)), "peer 1 should trust peer 2 after update") self.assertMIDList(context: self.cuttlefishContext, allowed: self.mockAuthKit.currentDeviceList(), disallowed: Set(), unknown: Set([self.mockAuthKit2.currentMachineID])) // On a follow-up update, peer1 should _not_ hit IDMS, even though there's an unknown peer ID in its DB let currentCount = self.mockAuthKit.fetchInvocations self.sendContainerChangeWaitForFetchForStates(context: self.cuttlefishContext, states: [OctagonStateReadyUpdated, OctagonStateReady]) self.assertMIDList(context: self.cuttlefishContext, allowed: self.mockAuthKit.currentDeviceList(), disallowed: Set(), unknown: Set([self.mockAuthKit2.currentMachineID])) XCTAssertEqual(currentCount, self.mockAuthKit.fetchInvocations, "Receving a push while having an unknown peer MID should not cause an AuthKit fetch") //////// // Then peer2 arrives on the device list. Peer 1 should update its dynamic info to no longer have a disposition for peer2. let updateTrustExpectation = self.expectation(description: "updateTrust") self.fakeCuttlefishServer.updateListener = { request in XCTAssertTrue(request.hasDynamicInfoAndSig, "updateTrust request should have a dynamic info") let newDynamicInfo = TPPeerDynamicInfo(data: request.dynamicInfoAndSig.peerDynamicInfo, sig: request.dynamicInfoAndSig.sig) XCTAssertNotNil(newDynamicInfo, "should be able to make a dynamic info from protobuf") // TODO: swift refuses to see the dispositions object on newDynamicInfo; ah well updateTrustExpectation.fulfill() return nil } self.mockAuthKit.addAndSendNotification(machineID: try! self.mockAuthKit2.machineID()) self.wait(for: [updateTrustExpectation], timeout: 10) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) XCTAssertTrue(self.fakeCuttlefishServer.assertCuttlefishState(FakeCuttlefishAssertion(peer: peer1ID, opinion: .trusts, target: peer2ID)), "peer 1 should trust peer 2 after update") } func testRemoveSelfWhenRemovedFromOnlySelfList() throws { self.startCKAccountStatusMock() self.mockAuthKit.otherDevices.removeAll() XCTAssertEqual(self.mockAuthKit.currentDeviceList(), Set([self.mockAuthKit.currentMachineID]), "AuthKit should have exactly one device on the list") let peer1ID = self.assertResetAndBecomeTrustedInDefaultContext() XCTAssertTrue(self.fakeCuttlefishServer.assertCuttlefishState(FakeCuttlefishAssertion(peer: peer1ID, opinion: .trusts, target: peer1ID)), "peer 1 should trust peer 1") // Then peer1 drops off the device list // It should remove trust in itself let updateTrustExpectation = self.expectation(description: "updateTrust") self.fakeCuttlefishServer.updateListener = { request in XCTAssertTrue(request.hasDynamicInfoAndSig, "updateTrust request should have a dynamic info") let newDynamicInfo = TPPeerDynamicInfo(data: request.dynamicInfoAndSig.peerDynamicInfo, sig: request.dynamicInfoAndSig.sig) XCTAssertNotNil(newDynamicInfo, "should be able to make a dynamic info from protobuf") XCTAssertEqual(newDynamicInfo!.includedPeerIDs.count, 0, "peer1 should no longer trust anyone") XCTAssertEqual(newDynamicInfo!.excludedPeerIDs, Set([peer1ID]), "peer1 should exclude itself") updateTrustExpectation.fulfill() return nil } self.mockAuthKit.removeAndSendNotification(machineID: try! self.mockAuthKit.machineID()) self.wait(for: [updateTrustExpectation], timeout: 10) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTrust, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfUntrusted(context: self.cuttlefishContext) } func testRemoveSelfWhenRemovedFromLargeDeviceList() throws { self.startCKAccountStatusMock() XCTAssert(self.mockAuthKit.currentDeviceList().count > 1, "AuthKit should have more than one device on the list") let peer1ID = self.assertResetAndBecomeTrustedInDefaultContext() XCTAssertTrue(self.fakeCuttlefishServer.assertCuttlefishState(FakeCuttlefishAssertion(peer: peer1ID, opinion: .trusts, target: peer1ID)), "peer 1 should trust peer 1") // Then peer1 drops off the device list // It should remove trust in itself let updateTrustExpectation = self.expectation(description: "updateTrust") self.fakeCuttlefishServer.updateListener = { request in XCTAssertTrue(request.hasDynamicInfoAndSig, "updateTrust request should have a dynamic info") let newDynamicInfo = TPPeerDynamicInfo(data: request.dynamicInfoAndSig.peerDynamicInfo, sig: request.dynamicInfoAndSig.sig) XCTAssertNotNil(newDynamicInfo, "should be able to make a dynamic info from protobuf") XCTAssertEqual(newDynamicInfo!.includedPeerIDs.count, 0, "peer1 should no longer trust anyone") XCTAssertEqual(newDynamicInfo!.excludedPeerIDs, Set([peer1ID]), "peer1 should exclude itself") updateTrustExpectation.fulfill() return nil } self.mockAuthKit.removeAndSendNotification(machineID: try! self.mockAuthKit.machineID()) self.wait(for: [updateTrustExpectation], timeout: 10) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTrust, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfUntrusted(context: self.cuttlefishContext) } func testRemoveSelfWhenRemovedFromLargeDeviceListByIncompleteNotification() throws { self.startCKAccountStatusMock() XCTAssert(self.mockAuthKit.currentDeviceList().count > 1, "AuthKit should have more than one device on the list") let peer1ID = self.assertResetAndBecomeTrustedInDefaultContext() XCTAssertTrue(self.fakeCuttlefishServer.assertCuttlefishState(FakeCuttlefishAssertion(peer: peer1ID, opinion: .trusts, target: peer1ID)), "peer 1 should trust peer 1") // Then peer1 drops off the device list // It should remove trust in itself let updateTrustExpectation = self.expectation(description: "updateTrust") self.fakeCuttlefishServer.updateListener = { request in XCTAssertTrue(request.hasDynamicInfoAndSig, "updateTrust request should have a dynamic info") let newDynamicInfo = TPPeerDynamicInfo(data: request.dynamicInfoAndSig.peerDynamicInfo, sig: request.dynamicInfoAndSig.sig) XCTAssertNotNil(newDynamicInfo, "should be able to make a dynamic info from protobuf") XCTAssertEqual(newDynamicInfo!.includedPeerIDs.count, 0, "peer1 should no longer trust anyone") XCTAssertEqual(newDynamicInfo!.excludedPeerIDs, Set([peer1ID]), "peer1 should exclude itself") updateTrustExpectation.fulfill() return nil } self.mockAuthKit.excludeDevices.insert(try! self.mockAuthKit.machineID()) XCTAssertFalse(self.mockAuthKit.currentDeviceList().contains(self.mockAuthKit.currentMachineID), "AuthKit should not still have device 2 on the list") self.mockAuthKit.sendIncompleteNotification() self.wait(for: [updateTrustExpectation], timeout: 10) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTrust, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfUntrusted(context: self.cuttlefishContext) } func testIgnoreRemoveFromWrongAltDSID() throws { self.startCKAccountStatusMock() XCTAssert(self.mockAuthKit.currentDeviceList().contains(self.mockAuthKit2.currentMachineID), "AuthKit should already have device 2 on the list") let peer1ID = self.assertResetAndBecomeTrustedInDefaultContext() let joiningContext = self.makeInitiatorContext(contextID: "joiner", authKitAdapter: self.mockAuthKit2) let peer2ID = self.assertJoinViaEscrowRecovery(joiningContext: joiningContext, sponsor: self.cuttlefishContext) // Now, tell peer1 about the change. It will trust the peer and upload some TLK shares self.assertAllCKKSViewsUpload(tlkShares: 1) self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext) assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) self.verifyDatabaseMocks() XCTAssertTrue(self.fakeCuttlefishServer.assertCuttlefishState(FakeCuttlefishAssertion(peer: peer1ID, opinion: .trusts, target: peer2ID)), "peer 1 should trust peer 2 after update") // We receive a 'remove' push for peer2's ID, but for the wrong DSID. The peer should do nothing useful. self.fakeCuttlefishServer.updateListener = { request in XCTFail("shouldn't have updated trust") return nil } self.mockAuthKit.sendRemoveNotification(machineID: try! self.mockAuthKit2.machineID(), altDSID: "completely-wrong") self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) XCTAssertTrue(self.fakeCuttlefishServer.assertCuttlefishState(FakeCuttlefishAssertion(peer: peer1ID, opinion: .trusts, target: peer2ID)), "peer 1 should trust peer 2 after update") self.assertMIDList(context: self.cuttlefishContext, allowed: self.mockAuthKit.currentDeviceList(), disallowed: Set(), unknown: Set()) } func testIgnoreAddFromWrongAltDSID() throws { self.startCKAccountStatusMock() XCTAssert(self.mockAuthKit.currentDeviceList().contains(self.mockAuthKit2.currentMachineID), "AuthKit should already have device 2 on the list") let peer1ID = self.assertResetAndBecomeTrustedInDefaultContext() let joiningContext = self.makeInitiatorContext(contextID: "joiner", authKitAdapter: self.mockAuthKit2) let peer2ID = self.assertJoinViaEscrowRecovery(joiningContext: joiningContext, sponsor: self.cuttlefishContext) // Now, tell peer1 about the change. It will trust the peer and upload some TLK shares self.assertAllCKKSViewsUpload(tlkShares: 1) self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext) assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) self.verifyDatabaseMocks() XCTAssertTrue(self.fakeCuttlefishServer.assertCuttlefishState(FakeCuttlefishAssertion(peer: peer1ID, opinion: .trusts, target: peer2ID)), "peer 1 should trust peer 2 after update") // We receive a 'add' push for a new ID, but for the wrong DSID. The peer should do nothing useful. self.fakeCuttlefishServer.updateListener = { request in XCTFail("shouldn't have updated trust") return nil } let newMachineID = "newID" self.mockAuthKit.sendAddNotification(machineID: newMachineID, altDSID: "completely-wrong") self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) XCTAssertTrue(self.fakeCuttlefishServer.assertCuttlefishState(FakeCuttlefishAssertion(peer: peer1ID, opinion: .trusts, target: peer2ID)), "peer 1 should trust peer 2 after update") // newMachineID should be on no lists self.assertMIDList(context: self.cuttlefishContext, allowed: self.mockAuthKit.currentDeviceList(), disallowed: Set(), unknown: Set()) } func testPeerJoiningWithUnknownMachineIDTriggersMachineIDFetchAfterGracePeriod() throws { self.startCKAccountStatusMock() // Peer 2 is not on Peer 1's machine ID list yet self.mockAuthKit.otherDevices.remove(self.mockAuthKit2.currentMachineID) _ = self.assertResetAndBecomeTrustedInDefaultContext() let joiningContext = self.makeInitiatorContext(contextID: "joiner", authKitAdapter: self.mockAuthKit2) _ = self.assertJoinViaEscrowRecovery(joiningContext: joiningContext, sponsor: self.cuttlefishContext) // Now, add peer2 to the machineID list, but don't send peer1 a notification about the IDMS change self.mockAuthKit.otherDevices.insert(self.mockAuthKit2.currentMachineID) self.assertMIDList(context: self.cuttlefishContext, allowed: self.mockAuthKit.currentDeviceList().subtracting(Set([self.mockAuthKit2.currentMachineID]))) let condition = CKKSCondition() self.mockAuthKit.fetchCondition = condition // But peer1 does get the cuttlefish push self.assertAllCKKSViewsUpload(tlkShares: 1) self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) self.verifyDatabaseMocks() // At this time, peer1 should trust peer2, but it should _not_ have fetched the AuthKit list, // because peer2 is still within the 48 hour grace period. peer1 is hoping for a push to arrive. XCTAssertNotEqual(condition.wait(2 * NSEC_PER_SEC), 0, "Octagon should not fetch the authkit machine ID list") let peer2MIDSet = Set([self.mockAuthKit2.currentMachineID]) self.assertMIDList(context: self.cuttlefishContext, allowed: self.mockAuthKit.currentDeviceList().subtracting(peer2MIDSet), unknown: peer2MIDSet) // Now, let's pretend that three days pass, and do this again... Octagon should now fetch the MID list and become happy. let container = try self.tphClient.getContainer(withContainer: self.cuttlefishContext.containerName, context: self.cuttlefishContext.contextID) container.moc.performAndWait { var foundPeer2 = false for machinemo in container.containerMO.machines as? Set ?? Set() { if machinemo.machineID == self.mockAuthKit2.currentMachineID { foundPeer2 = true // machinemo.modified = Date(timeIntervalSinceNow: -60 * 60 * TimeInterval(72)) XCTAssertEqual(machinemo.status, Int64(TPMachineIDStatus.unknown.rawValue), "peer2's MID entry should be 'unknown'") } } XCTAssertTrue(foundPeer2, "Should have found an entry for peer2") try! container.moc.save() } self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) XCTAssertEqual(condition.wait(10 * NSEC_PER_SEC), 0, "Octagon should fetch the authkit machine ID list") self.assertMIDList(context: self.cuttlefishContext, allowed: self.mockAuthKit.currentDeviceList()) } func testTrustPeerWheMissingFromDeviceListAndLocked() throws { self.startCKAccountStatusMock() self.mockAuthKit.otherDevices.removeAll() XCTAssertEqual(self.mockAuthKit.currentDeviceList(), Set([self.mockAuthKit.currentMachineID]), "AuthKit should have exactly one device on the list") XCTAssertFalse(self.mockAuthKit.currentDeviceList().contains(self.mockAuthKit2.currentMachineID), "AuthKit should not already have device 2 on the list") let peer1ID = self.assertResetAndBecomeTrustedInDefaultContext() let joiningContext = self.makeInitiatorContext(contextID: "joiner", authKitAdapter: self.mockAuthKit2) let peer2ID = self.assertJoinViaEscrowRecovery(joiningContext: joiningContext, sponsor: self.cuttlefishContext) // Now, tell peer1 about the change. It will trust the peer, despite it missing from the list, and upload some TLK shares self.assertAllCKKSViewsUpload(tlkShares: 1) self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext) self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) self.verifyDatabaseMocks() XCTAssertTrue(self.fakeCuttlefishServer.assertCuttlefishState(FakeCuttlefishAssertion(peer: peer1ID, opinion: .trusts, target: peer2ID)), "peer 1 should trust peer 2 after update") // Then peer2 arrives on the device list. Peer 1 should update its dynamic info to no longer have a disposition for peer2. let updateTrustExpectation = self.expectation(description: "updateTrust") self.fakeCuttlefishServer.updateListener = { request in XCTAssertTrue(request.hasDynamicInfoAndSig, "updateTrust request should have a dynamic info") let newDynamicInfo = TPPeerDynamicInfo(data: request.dynamicInfoAndSig.peerDynamicInfo, sig: request.dynamicInfoAndSig.sig) XCTAssertNotNil(newDynamicInfo, "should be able to make a dynamic info from protobuf") // TODO: swift refuses to see the dispositions object on newDynamicInfo; ah well updateTrustExpectation.fulfill() return nil } // Now, peer should lock and receive an Octagon push self.aksLockState = true self.lockStateTracker.recheck() // Now, peer2 should receive an Octagon push, try to realize it is preapproved, and get stuck self.sendContainerChange(context: self.cuttlefishContext) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.mockAuthKit.addAndSendNotification(machineID: try! self.mockAuthKit2.machineID()) sleep(1) XCTAssertTrue(self.cuttlefishContext.stateMachine.possiblePendingFlags().contains("recd_push"), "Should have recd_push pending flag") // Now, peer should unlock and receive an Octagon push self.aksLockState = false self.lockStateTracker.recheck() sleep(1) XCTAssertEqual(self.cuttlefishContext.stateMachine.possiblePendingFlags(), [], "Should have 0 pending flags") self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.wait(for: [updateTrustExpectation], timeout: 10) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) XCTAssertTrue(self.fakeCuttlefishServer.assertCuttlefishState(FakeCuttlefishAssertion(peer: peer1ID, opinion: .trusts, target: peer2ID)), "peer 1 should trust peer 2 after update") } } #endif