#if OCTAGON class OctagonResetTests: OctagonTestsBase { func testAccountAvailableAndHandleExternalCall() throws { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) _ = try self.cuttlefishContext.accountAvailable("13453464") self.cuttlefishContext.rpcResetAndEstablish(.testGenerated) { resetError in XCTAssertNil(resetError, "should be no error resetting and establishing") } self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.verifyDatabaseMocks() } func testExernalCallAndAccountAvailable() throws { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.cuttlefishContext.rpcResetAndEstablish(.testGenerated) { resetError in XCTAssertNil(resetError, "should be no error resetting and establishing") } _ = try self.cuttlefishContext.accountAvailable("13453464") self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.verifyDatabaseMocks() } func testCallingAccountAvailableDuringResetAndEstablish() throws { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.cuttlefishContext.rpcResetAndEstablish(.testGenerated) { resetError in XCTAssertNil(resetError, "should be no error resetting and establishing") } self.assertEnters(context: self.cuttlefishContext, state: OctagonStateResetAndEstablish, within: 1 * NSEC_PER_SEC) _ = try self.cuttlefishContext.accountAvailable("13453464") self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.verifyDatabaseMocks() } func testResetAndEstablishWithEscrow() throws { let contextName = OTDefaultContext let containerName = OTCKContainerName self.startCKAccountStatusMock() // Before resetAndEstablish, there shouldn't be any stored account state XCTAssertThrowsError(try OTAccountMetadataClassC.loadFromKeychain(forContainer: containerName, contextID: contextName), "Before doing anything, loading a non-existent account state should fail") let resetAndEstablishExpectation = self.expectation(description: "resetAndEstablish callback occurs") let escrowRequestNotification = expectation(forNotification: OTMockEscrowRequestNotification, object: nil, handler: nil) self.manager.resetAndEstablish(containerName, context: contextName, altDSID: "new altDSID", resetReason: .testGenerated) { resetError in XCTAssertNil(resetError, "Should be no error calling resetAndEstablish") resetAndEstablishExpectation.fulfill() } self.wait(for: [resetAndEstablishExpectation], timeout: 10) self.wait(for: [escrowRequestNotification], timeout: 5) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) let selfPeerID = try self.cuttlefishContext.accountMetadataStore.loadOrCreateAccountMetadata().peerID // After resetAndEstablish, you should be able to see the persisted account state do { let accountState = try OTAccountMetadataClassC.loadFromKeychain(forContainer: containerName, contextID: contextName) XCTAssertEqual(selfPeerID, accountState.peerID, "Saved account state should have the same peer ID that prepare returned") } catch { XCTFail("error loading account state: \(error)") } } func testResetAndEstablishStopsCKKS() throws { let contextName = OTDefaultContext let containerName = OTCKContainerName self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) 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... assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) self.verifyDatabaseMocks() // CKKS should pass through "waitfortrust" during a reset let waitfortrusts = self.ckksViews.compactMap { view in (view as! CKKSKeychainView).keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTrust] as? CKKSCondition } XCTAssert(!waitfortrusts.isEmpty, "Should have at least one waitfortrust condition") let resetAndEstablishExpectation = self.expectation(description: "resetAndEstablish callback occurs") let escrowRequestNotification = expectation(forNotification: OTMockEscrowRequestNotification, object: nil, handler: nil) self.manager.resetAndEstablish(containerName, context: contextName, altDSID: "new altDSID", resetReason: .testGenerated) { resetError in XCTAssertNil(resetError, "Should be no error calling resetAndEstablish") resetAndEstablishExpectation.fulfill() } self.wait(for: [resetAndEstablishExpectation], timeout: 10) self.wait(for: [escrowRequestNotification], timeout: 5) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) // CKKS should have all gone into waitfortrust during that time for condition in waitfortrusts { XCTAssertEqual(0, condition.wait(10 * NSEC_PER_MSEC), "CKKS should have entered waitfortrust") } } func testOctagonResetAlsoResetsCKKSViewsMissingTLKs() { self.putFakeKeyHierarchiesInCloudKit() let zoneKeys = self.keys![self.limitedPeersAllowedZoneID!] as? ZoneKeys XCTAssertNotNil(zoneKeys, "Should have some zone keys") XCTAssertNotNil(zoneKeys?.tlk, "Should have a tlk in the original key set") self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTrust, within: 10 * NSEC_PER_SEC) self.silentZoneDeletesAllowed = true do { _ = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated) } catch { XCTFail("failed to make new friends: \(error)") } assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) let laterZoneKeys = self.keys![self.limitedPeersAllowedZoneID!] as? ZoneKeys XCTAssertNotNil(laterZoneKeys, "Should have some zone keys") XCTAssertNotNil(laterZoneKeys?.tlk, "Should have a tlk in the newly created keyset") XCTAssertNotEqual(zoneKeys?.tlk?.uuid, laterZoneKeys?.tlk?.uuid, "CKKS zone should now have different keys") } func testOctagonResetIgnoresOldRemoteDevicesWithKeysAndResetsCKKS() { // CKKS has no keys, and there's another device claiming to have them already, but it's old self.putFakeKeyHierarchiesInCloudKit() self.putFakeDeviceStatusesInCloudKit() #if !os(tvOS) (self.zones![self.manateeZoneID!]! as! FakeCKZone).currentDatabase.allValues.forEach { record in let r = record as! CKRecord if r.recordType == SecCKRecordDeviceStateType { r.creationDate = NSDate.distantPast r.modificationDate = NSDate.distantPast } } #endif (self.zones![self.limitedPeersAllowedZoneID!]! as! FakeCKZone).currentDatabase.allValues.forEach { record in let r = record as! CKRecord if r.recordType == SecCKRecordDeviceStateType { r.creationDate = NSDate.distantPast r.modificationDate = NSDate.distantPast } } #if !os(tvOS) let zoneKeys = self.keys![self.manateeZoneID!] as? ZoneKeys XCTAssertNotNil(zoneKeys, "Should have some zone keys for Manatee") XCTAssertNotNil(zoneKeys?.tlk, "Should have a tlk in the original key set for Manatee") #endif let lpZoneKeys = self.keys![self.limitedPeersAllowedZoneID!] as? ZoneKeys XCTAssertNotNil(lpZoneKeys, "Should have some zone keys for LimitedPeers") XCTAssertNotNil(lpZoneKeys?.tlk, "Should have a tlk in the original key set for LimitedPeers") self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.silentZoneDeletesAllowed = true do { _ = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated) } catch { XCTFail("failed to make new friends: \(error)") } self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) #if !os(tvOS) let laterZoneKeys = self.keys![self.manateeZoneID!] as? ZoneKeys XCTAssertNotNil(laterZoneKeys, "Should have some zone keys for Manatee") XCTAssertNotNil(laterZoneKeys?.tlk, "Should have a tlk in the newly created keyset for Manatee") XCTAssertNotEqual(zoneKeys?.tlk?.uuid, laterZoneKeys?.tlk?.uuid, "CKKS zone should now have different keys for Manatee") #else let laterZoneKeys = self.keys![self.manateeZoneID!] as? ZoneKeys XCTAssertNil(laterZoneKeys, "Should have no Manatee zone keys for aTV") #endif let laterLpZoneKeys = self.keys![self.limitedPeersAllowedZoneID!] as? ZoneKeys XCTAssertNotNil(laterLpZoneKeys, "Should have some zone keys for LimitedPeers") XCTAssertNotNil(laterLpZoneKeys?.tlk, "Should have a tlk in the newly created keyset for LimitedPeers") XCTAssertNotEqual(lpZoneKeys?.tlk?.uuid, laterLpZoneKeys?.tlk?.uuid, "CKKS zone should now have different keys for LimitedPeers") } func testOctagonResetWithRemoteDevicesWithKeysDoesNotResetCKKS() { // CKKS has no keys, and there's another device claiming to have them already, so CKKS won't immediately reset it self.putFakeKeyHierarchiesInCloudKit() self.putFakeDeviceStatusesInCloudKit() #if !os(tvOS) let zoneKeys = self.keys![self.manateeZoneID!] as? ZoneKeys XCTAssertNotNil(zoneKeys, "Should have some zone keys") XCTAssertNotNil(zoneKeys?.tlk, "Should have a tlk in the original key set") #endif let lpZoneKeys = self.keys![self.limitedPeersAllowedZoneID!] as? ZoneKeys XCTAssertNotNil(lpZoneKeys, "Should have some zone keys for LimitedPeers") XCTAssertNotNil(lpZoneKeys?.tlk, "Should have a tlk in the original key set for LimitedPeers") self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.silentZoneDeletesAllowed = true do { _ = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated) } catch { XCTFail("failed to make new friends: \(error)") } assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLK, within: 10 * NSEC_PER_SEC) #if !os(tvOS) let laterZoneKeys = self.keys![self.manateeZoneID!] as? ZoneKeys XCTAssertNotNil(laterZoneKeys, "Should have some zone keys") XCTAssertNotNil(laterZoneKeys?.tlk, "Should have a tlk in the newly created keyset") XCTAssertEqual(zoneKeys?.tlk?.uuid, laterZoneKeys?.tlk?.uuid, "CKKS zone should now have the same keys") #else let laterZoneKeys = self.keys![self.manateeZoneID!] as? ZoneKeys XCTAssertNil(laterZoneKeys, "Should have no Manatee zone keys for aTV") #endif let lpLaterZoneKeys = self.keys![self.limitedPeersAllowedZoneID!] as? ZoneKeys XCTAssertNotNil(lpLaterZoneKeys, "Should have some zone keys for LimitedPeersAllowed") XCTAssertNotNil(lpLaterZoneKeys?.tlk, "Should have a tlk in the newly created keyset for LimitedPeersAllowed") XCTAssertEqual(lpZoneKeys?.tlk?.uuid, lpLaterZoneKeys?.tlk?.uuid, "CKKS zone should now have the same keys for LimitedPeersAllowed") } func testOctagonResetWithTLKsDoesNotResetCKKS() { // CKKS has the keys keys self.putFakeKeyHierarchiesInCloudKit() self.saveTLKMaterialToKeychain() #if !os(tvOS) let zoneKeys = self.keys![self.manateeZoneID!] as? ZoneKeys XCTAssertNotNil(zoneKeys, "Should have some zone keys") XCTAssertNotNil(zoneKeys?.tlk, "Should have a tlk in the original key set") #endif let lpZoneKeys = self.keys![self.limitedPeersAllowedZoneID!] as? ZoneKeys XCTAssertNotNil(lpZoneKeys, "Should have some zone keys for LimitedPeers") XCTAssertNotNil(lpZoneKeys?.tlk, "Should have a tlk in the original key set for LimitedPeers") self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) do { _ = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated) } catch { XCTFail("failed to make new friends: \(error)") } self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) #if !os(tvOS) let laterZoneKeys = self.keys![self.manateeZoneID!] as? ZoneKeys XCTAssertNotNil(laterZoneKeys, "Should have some zone keys") XCTAssertNotNil(laterZoneKeys?.tlk, "Should have a tlk in the newly created keyset") XCTAssertEqual(zoneKeys?.tlk?.uuid, laterZoneKeys?.tlk?.uuid, "CKKS zone should now have the same keys") #else let laterZoneKeys = self.keys![self.manateeZoneID!] as? ZoneKeys XCTAssertNil(laterZoneKeys, "Should have no Manatee zone keys for aTV") #endif let lpLaterZoneKeys = self.keys![self.limitedPeersAllowedZoneID!] as? ZoneKeys XCTAssertNotNil(lpLaterZoneKeys, "Should have some zone keys for LimitedPeersAllowed") XCTAssertNotNil(lpLaterZoneKeys?.tlk, "Should have a tlk in the newly created keyset for LimitedPeersAllowed") XCTAssertEqual(lpZoneKeys?.tlk?.uuid, lpLaterZoneKeys?.tlk?.uuid, "CKKS zone should now have the same keys for LimitedPeersAllowed") } func testOctagonResetAndEstablishFail() throws { // Make sure if establish fail we end up in untrusted instead of error self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) _ = try self.cuttlefishContext.accountAvailable("13453464") let establishExpectation = self.expectation(description: "establishExpectation") let resetExpectation = self.expectation(description: "resetExpectation") self.fakeCuttlefishServer.establishListener = { [unowned self] request in self.fakeCuttlefishServer.establishListener = nil establishExpectation.fulfill() return FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .establishFailed) } self.cuttlefishContext.rpcResetAndEstablish(.testGenerated) { resetError in resetExpectation.fulfill() XCTAssertNotNil(resetError, "should error resetting and establishing") } self.wait(for: [establishExpectation, resetExpectation], timeout: 10) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.verifyDatabaseMocks() } func testResetReasonUnknown() throws { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) _ = try self.cuttlefishContext.accountAvailable("13453464") let resetExpectation = self.expectation(description: "resetExpectation") self.fakeCuttlefishServer.resetListener = { request in self.fakeCuttlefishServer.resetListener = nil resetExpectation.fulfill() XCTAssertTrue(request.resetReason.rawValue == CuttlefishResetReason.unknown.rawValue, "reset reason should be unknown") return nil } let establishAndResetExpectation = self.expectation(description: "resetExpectation") self.cuttlefishContext.rpcResetAndEstablish(.unknown) { resetError in establishAndResetExpectation.fulfill() XCTAssertNil(resetError, "should not error resetting and establishing") } self.wait(for: [establishAndResetExpectation, resetExpectation], timeout: 10) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.verifyDatabaseMocks() } func testResetReasonUserInitiatedReset() throws { // Make sure if establish fail we end up in untrusted instead of error self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) _ = try self.cuttlefishContext.accountAvailable("13453464") let resetExpectation = self.expectation(description: "resetExpectation") self.fakeCuttlefishServer.resetListener = { request in self.fakeCuttlefishServer.resetListener = nil resetExpectation.fulfill() XCTAssertTrue(request.resetReason.rawValue == CuttlefishResetReason.userInitiatedReset.rawValue, "reset reason should be user initiated reset") return nil } let establishAndResetExpectation = self.expectation(description: "resetExpectation") let clique: OTClique let recoverykeyotcliqueContext = OTConfigurationContext() recoverykeyotcliqueContext.context = OTDefaultContext recoverykeyotcliqueContext.dsid = "13453464" recoverykeyotcliqueContext.altDSID = self.mockAuthKit.altDSID! recoverykeyotcliqueContext.otControl = self.otControl do { clique = try OTClique.newFriends(withContextData: recoverykeyotcliqueContext, resetReason: .userInitiatedReset) XCTAssertNotNil(clique, "Clique should not be nil") XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call") establishAndResetExpectation.fulfill() } catch { XCTFail("Shouldn't have errored making new friends: \(error)") throw error } self.wait(for: [establishAndResetExpectation, resetExpectation], timeout: 10) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.verifyDatabaseMocks() } func testResetReasonRecoveryKey() throws { // Make sure if establish fail we end up in untrusted instead of error self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) _ = try self.cuttlefishContext.accountAvailable("13453464") let resetExpectation = self.expectation(description: "resetExpectation") self.fakeCuttlefishServer.resetListener = { request in self.fakeCuttlefishServer.resetListener = nil resetExpectation.fulfill() XCTAssertTrue(request.resetReason.rawValue == CuttlefishResetReason.recoveryKey.rawValue, "reset reason should be recovery key") return nil } let recoveryKey = SecPasswordGenerate(SecPasswordType(kSecPasswordTypeiCloudRecoveryKey), nil, nil)! as String XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil") let newCliqueContext = OTConfigurationContext() newCliqueContext.context = OTDefaultContext newCliqueContext.dsid = self.otcliqueContext.dsid newCliqueContext.altDSID = self.mockAuthKit.altDSID! newCliqueContext.otControl = self.otControl let joinWithRecoveryKeyExpectation = self.expectation(description: "joinWithRecoveryKeyExpectation callback occurs") TestsObjectiveC.recoverOctagon(usingData: newCliqueContext, recoveryKey: recoveryKey) { error in XCTAssertNil(error, "error should be nil") joinWithRecoveryKeyExpectation.fulfill() } self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10) self.wait(for: [resetExpectation], timeout: 10) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.verifyDatabaseMocks() } func testResetReasonNoValidBottle() throws { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let initiatorContext = self.manager.context(forContainerName: OTCKContainerName, contextID: "restoreContext", sosAdapter: OTSOSMissingAdapter(), authKitAdapter: self.mockAuthKit2, lockStateTracker: self.lockStateTracker, accountStateTracker: self.accountStateTracker, deviceInformationAdapter: self.makeInitiatorDeviceInfoAdapter()) initiatorContext.startOctagonStateMachine() let newOTCliqueContext = OTConfigurationContext() newOTCliqueContext.context = OTDefaultContext newOTCliqueContext.dsid = self.otcliqueContext.dsid newOTCliqueContext.altDSID = self.otcliqueContext.altDSID newOTCliqueContext.otControl = self.otcliqueContext.otControl newOTCliqueContext.sbd = OTMockSecureBackup(bottleID: nil, entropy: nil) let resetExpectation = self.expectation(description: "resetExpectation callback occurs") self.fakeCuttlefishServer.resetListener = { request in self.fakeCuttlefishServer.resetListener = nil resetExpectation.fulfill() XCTAssertTrue(request.resetReason.rawValue == CuttlefishResetReason.noBottleDuringEscrowRecovery.rawValue, "reset reason should be no bottle during escrow recovery") return nil } let newClique: OTClique do { newClique = try OTClique.performEscrowRecovery(withContextData: newOTCliqueContext, escrowArguments: [:]) XCTAssertNotNil(newClique, "newClique should not be nil") } catch { XCTFail("Shouldn't have errored recovering: \(error)") throw error } self.wait(for: [resetExpectation], timeout: 10) } func testResetReasonHealthCheck() throws { let containerName = OTCKContainerName let contextName = OTDefaultContext self.cuttlefishContext.startOctagonStateMachine() XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.startCKAccountStatusMock() self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique do { clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated) XCTAssertNotNil(clique, "Clique should not be nil") XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call") } catch { XCTFail("Shouldn't have errored making new friends: \(error)") throw error } self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfTrusted(context: self.cuttlefishContext) do { let accountState = try OTAccountMetadataClassC.loadFromKeychain(forContainer: containerName, contextID: contextName) XCTAssertEqual(2, accountState.trustState.rawValue, "saved account should be trusted") } catch { XCTFail("error loading account state: \(error)") } // Reset any CFUs we've done so far self.otFollowUpController.postedFollowUp = false let resetExpectation = self.expectation(description: "resetExpectation callback occurs") self.fakeCuttlefishServer.resetListener = { request in self.fakeCuttlefishServer.resetListener = nil resetExpectation.fulfill() XCTAssertTrue(request.resetReason.rawValue == CuttlefishResetReason.healthCheck.rawValue, "reset reason should be health check") return nil } self.fakeCuttlefishServer.returnResetOctagonResponse = true self.aksLockState = false self.lockStateTracker.recheck() let healthCheckCallback = self.expectation(description: "healthCheckCallback callback occurs") self.manager.healthCheck(containerName, context: contextName, skipRateLimitingCheck: false) { error in XCTAssertNil(error, "error should be nil") healthCheckCallback.fulfill() } self.wait(for: [healthCheckCallback, resetExpectation], timeout: 10) assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) self.verifyDatabaseMocks() let dumpCallback = self.expectation(description: "dumpCallback callback occurs") self.tphClient.dump(withContainer: containerName, context: contextName) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") let egoSelf = dump!["self"] as? [String: AnyObject] XCTAssertNotNil(egoSelf, "egoSelf should not be nil") dumpCallback.fulfill() } self.wait(for: [dumpCallback], timeout: 10) self.verifyDatabaseMocks() self.assertEnters(context: cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) } func testLegacyJoinCircleDoesNotReset() throws { self.cuttlefishContext.startOctagonStateMachine() self.startCKAccountStatusMock() XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let establishAndResetExpectation = self.expectation(description: "resetExpectation") let clique: OTClique let recoverykeyotcliqueContext = OTConfigurationContext() recoverykeyotcliqueContext.context = OTDefaultContext recoverykeyotcliqueContext.dsid = "13453464" recoverykeyotcliqueContext.altDSID = self.mockAuthKit.altDSID! recoverykeyotcliqueContext.otControl = self.otControl do { clique = try OTClique.newFriends(withContextData: recoverykeyotcliqueContext, resetReason: .testGenerated) XCTAssertNotNil(clique, "Clique should not be nil") XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call") establishAndResetExpectation.fulfill() } catch { XCTFail("Shouldn't have errored making new friends: \(error)") throw error } self.wait(for: [establishAndResetExpectation], timeout: 10) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.verifyDatabaseMocks() self.fakeCuttlefishServer.resetListener = { request in XCTFail("requestToJoinCircle should not reset Octagon") return nil } do { _ = try clique.requestToJoinCircle() } catch { XCTFail("Shouldn't have errored requesting to join circle: \(error)") throw error } self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) } func testResetReasonTestGenerated() throws { self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) _ = try self.cuttlefishContext.accountAvailable("13453464") let resetExpectation = self.expectation(description: "resetExpectation") self.fakeCuttlefishServer.resetListener = { request in self.fakeCuttlefishServer.resetListener = nil resetExpectation.fulfill() XCTAssertTrue(request.resetReason.rawValue == CuttlefishResetReason.testGenerated.rawValue, "reset reason should be test generated") return nil } let establishAndResetExpectation = self.expectation(description: "resetExpectation") self.cuttlefishContext.rpcResetAndEstablish(.testGenerated) { resetError in establishAndResetExpectation.fulfill() XCTAssertNil(resetError, "should not error resetting and establishing") } self.wait(for: [establishAndResetExpectation, resetExpectation], timeout: 10) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.verifyDatabaseMocks() } func testCliqueResetAllSPI() throws { self.cuttlefishContext.startOctagonStateMachine() self.startCKAccountStatusMock() OctagonSetSOSFeatureEnabled(false) XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled()) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let establishAndResetExpectation = self.expectation(description: "resetExpectation") let clique: OTClique let otcliqueContext = OTConfigurationContext() var firstCliqueIdentifier: String? otcliqueContext.context = OTDefaultContext otcliqueContext.dsid = "13453464" otcliqueContext.altDSID = self.mockAuthKit.altDSID! otcliqueContext.authenticationAppleID = "appleID" otcliqueContext.passwordEquivalentToken = "petpetpetpetpet" otcliqueContext.otControl = self.otControl otcliqueContext.ckksControl = self.ckksControl otcliqueContext.sbd = OTMockSecureBackup(bottleID: nil, entropy: nil) do { clique = try OTClique.newFriends(withContextData: otcliqueContext, resetReason: .testGenerated) XCTAssertNotNil(clique, "Clique should not be nil") XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call") firstCliqueIdentifier = clique.cliqueMemberIdentifier establishAndResetExpectation.fulfill() } catch { XCTFail("Shouldn't have errored making new friends everything: \(error)") throw error } self.wait(for: [establishAndResetExpectation], timeout: 10) self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) self.verifyDatabaseMocks() self.silentZoneDeletesAllowed = true let newClique: OTClique do { newClique = try OTClique.resetProtectedData(otcliqueContext) XCTAssertNotEqual(newClique.cliqueMemberIdentifier, firstCliqueIdentifier, "clique identifiers should be different") } catch { XCTFail("Shouldn't have errored resetting everything: \(error)") throw error } XCTAssertNotNil(newClique, "newClique should not be nil") self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) self.verifyDatabaseMocks() } func testCliqueResetProtectedDataHandlingInMultiPeerCircle() throws { OctagonSetSOSFeatureEnabled(false) self.startCKAccountStatusMock() var firstCliqueIdentifier: String? let clique: OTClique let cliqueContextConfiguration = OTConfigurationContext() cliqueContextConfiguration.context = OTDefaultContext cliqueContextConfiguration.dsid = "13453464" cliqueContextConfiguration.altDSID = self.mockAuthKit.altDSID! cliqueContextConfiguration.authenticationAppleID = "appleID" cliqueContextConfiguration.passwordEquivalentToken = "petpetpetpetpet" cliqueContextConfiguration.otControl = self.otControl cliqueContextConfiguration.ckksControl = self.ckksControl cliqueContextConfiguration.sbd = OTMockSecureBackup(bottleID: nil, entropy: nil) do { clique = try OTClique.newFriends(withContextData: cliqueContextConfiguration, resetReason: .testGenerated) XCTAssertNotNil(clique, "Clique should not be nil") XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call") firstCliqueIdentifier = clique.cliqueMemberIdentifier } catch { XCTFail("Shouldn't have errored making new friends: \(error)") throw error } self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfTrusted(context: self.cuttlefishContext) let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!) XCTAssertNotNil(entropy, "entropy should not be nil") let bottleJoinerContextID = "bottleJoiner" let joinerContext = self.manager.context(forContainerName: OTCKContainerName, contextID: bottleJoinerContextID) let bottle = self.fakeCuttlefishServer.state.bottles[0] joinerContext.startOctagonStateMachine() self.startCKAccountStatusMock() self.assertEnters(context: joinerContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) // Before you call joinWithBottle, you need to call fetchViableBottles. let fetchViableExpectation = self.expectation(description: "fetchViableBottles callback occurs") joinerContext.rpcFetchAllViableBottles { viable, _, error in XCTAssertNil(error, "should be no error fetching viable bottles") XCTAssert(viable?.contains(bottle.bottleID) ?? false, "The bottle we're about to restore should be viable") fetchViableExpectation.fulfill() } self.wait(for: [fetchViableExpectation], timeout: 10) let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs") joinerContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.mockAuthKit.altDSID!) { error in XCTAssertNil(error, "error should be nil") joinWithBottleExpectation.fulfill() } self.wait(for: [joinWithBottleExpectation], timeout: 10) self.assertEnters(context: joinerContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfTrusted(context:joinerContext) self.silentZoneDeletesAllowed = true let newClique: OTClique do { newClique = try OTClique.resetProtectedData(cliqueContextConfiguration) XCTAssertNotEqual(newClique.cliqueMemberIdentifier, firstCliqueIdentifier, "clique identifiers should be different") } catch { XCTFail("Shouldn't have errored resetting everything: \(error)") throw error } XCTAssertNotNil(newClique, "newClique should not be nil") self.sendContainerChangeWaitForFetchForStates(context: joinerContext, states: [OctagonStateUntrusted]) self.sendContainerChangeWaitForFetchForStates(context: self.cuttlefishContext, states: [OctagonStateReady]) self.assertConsidersSelfTrusted(context:self.cuttlefishContext) let statusExpectation = self.expectation(description: "status callback occurs") let configuration = OTOperationConfiguration() joinerContext.rpcTrustStatus(configuration) { egoStatus, _, _, _, _ in XCTAssertEqual(.notIn, egoStatus, "cliqueStatus should be 'Not In'") statusExpectation.fulfill() } self.wait(for: [statusExpectation], timeout: 10) } } #endif // OCTAGON