import { serverFactory, getEchoVulnConfig, getNoPasswordConfig, getDefaultPasswordConfig, getMastermindHintConfig, encodeNumberInBaseN, parseBaseNNumberString, getConvertToBase10Config, parseSimpleArithmeticExpression, generateSimpleArithmeticExpression, getLargestPrimeFactorConfig, largePrimes, getDivisibilityTestConfig, getRomanNumeralConfig, romanNumeralDecoder, getBufferOverflowConfig, getParseArithmeticExpressionConfig, getBinaryEncodedConfig, getTimingAttackConfig, getSpiceLevelConfig, getGuessNumberConfig, getCaptchaConfig, getYesn_tConfig, cleanArithmeticExpression, getXorMaskEncryptedPasswordConfig, getTripleModuloConfig, getKingOfTheHillConfig, getSortedEchoVulnConfig, } from "../../../src/DarkNet/controllers/ServerGenerator"; import { commonPasswordDictionary, connectors, defaultSettingsDictionary, l33t, lettersLowercase, lettersUppercase, loreNames, notebookFileNames, numbers, passwordFileNames, presetNames, ServerNamePrefixes, ServerNameSuffixes, } from "../../../src/DarkNet/models/dictionaryData"; import { getAuthResult, isCloseToCorrectPassword } from "../../../src/DarkNet/effects/authentication"; import { DarknetState } from "../../../src/DarkNet/models/DarknetState"; import { GenericResponseMessage, ResponseCodeEnum } from "../../../src/DarkNet/Enums"; import { expectWithMessage, getNS, initGameEnvironment, setupBasicTestingEnvironment } from "../Utilities"; import { getClueFileName, getDarkscapeNavigator } from "../../../src/DarkNet/effects/effects"; import * as exceptionAlertModule from "../../../src/utils/helpers/exceptionAlert"; import * as UtilityModule from "../../../src/utils/Utility"; import { mutateDarknet } from "../../../src/DarkNet/controllers/NetworkMovement"; import { launchWebstorm } from "../../../src/DarkNet/effects/webstorm"; import { isNumber } from "../../../src/types"; import { getMostRecentAuthLog, getServerLogs } from "../../../src/DarkNet/models/packetSniffing"; import { Player } from "@player"; import { assertString } from "../../../src/utils/TypeAssertion"; import { assertPasswordResponse, generateDarknetServerName } from "../../../src/DarkNet/models/DarknetServerOptions"; import { DarknetServer } from "../../../src/Server/DarknetServer"; import { isDirectoryPath } from "../../../src/Paths/Directory"; import { isFilePath } from "../../../src/Paths/FilePath"; import { LAB_CACHE_NAME } from "../../../src/DarkNet/effects/labyrinth"; import { generateCacheFilename } from "../../../src/DarkNet/effects/cacheFiles"; import { getAllDarknetServers } from "../../../src/DarkNet/utils/darknetNetworkUtils"; import { prestigeAugmentation } from "../../../src/Prestige"; beforeAll(() => { initGameEnvironment(); }); beforeEach(() => { setupBasicTestingEnvironment(); getDarkscapeNavigator(); }); afterEach(() => { jest.clearAllMocks(); }); const getLatestResponseTime = (server: DarknetServer) => { const lastPasswordResponse = getServerLogs(server, 1, true)[0].message; assertPasswordResponse(lastPasswordResponse); assertString(lastPasswordResponse.data); const timeMatch = lastPasswordResponse.data.match(/Response time: (\d+\.?\d*)ms/); if (!timeMatch) { throw new Error(`No response time found in log: ${JSON.stringify(lastPasswordResponse)}`); } const responseTime = Number(timeMatch[1]); if (!Number.isFinite(responseTime)) { throw new Error(`No response time found in log: ${JSON.stringify(lastPasswordResponse)}`); } return responseTime; }; describe("Password Tests", () => { const difficulty = 1; test("getEchoVulnServer creates a server and checks password correctly", () => { const server = serverFactory(getEchoVulnConfig, difficulty, 0, 0); expect(server).toBeDefined(); const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1); expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure); expect(failedAttemptResponse.response.message.includes(server.password)).toBe(true); expect(server.hasAdminRights).toBe(false); expect(getAuthResult(server, server.password, 1).result.code).toBe(ResponseCodeEnum.Success); expect(server.hasAdminRights).toBe(true); }); test("getNoPasswordServer creates a server with no password", () => { const server = serverFactory(getNoPasswordConfig, difficulty, 0, 0); expect(server).toBeDefined(); const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1); expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure); expect(server.hasAdminRights).toBe(false); expect(getAuthResult(server, server.password, 1).result.code).toBe(ResponseCodeEnum.Success); expect(server.hasAdminRights).toBe(true); }); test("getDefaultPasswordServer creates a server with default password", () => { const server = serverFactory(getDefaultPasswordConfig, difficulty, 0, 0); expect(server).toBeDefined(); const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1); expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure); expect(server.hasAdminRights).toBe(false); expect((defaultSettingsDictionary as unknown as string[]).includes(server.password)).toBe(true); expect(getAuthResult(server, server.password, 1).result.code).toBe(ResponseCodeEnum.Success); expect(server.hasAdminRights).toBe(true); }); test("getMastermindHintServer creates a server with mastermind hint", () => { const password = "11223334"; const server = serverFactory(getMastermindHintConfig, difficulty, 0, 0); server.password = password; expect(server).toBeDefined(); const getData = () => { const authLog = getMostRecentAuthLog(server.hostname); if (!authLog) { throw new Error(`Cannot find the most recent auth log in ${server.hostname}`); } if (!authLog.data) { throw new Error(`Auth log does not contain data: ${JSON.stringify(authLog)}`); } assertString(authLog.data); return authLog.data.split(",").map((item) => item.trim()); }; const failedAttemptResponse1 = getAuthResult(server, ""); expect(failedAttemptResponse1.response.code).toBe(ResponseCodeEnum.AuthFailure); expect(server.hasAdminRights).toBe(false); const [correctCount1, closeCount1] = getData(); expect(correctCount1).toBe("0"); expect(closeCount1).toBe("0"); const failedAttemptResponse2 = getAuthResult(server, "123"); expect(failedAttemptResponse2.response.code).toBe(ResponseCodeEnum.AuthFailure); const [correctCount2, closeCount2] = getData(); expect(correctCount2).toBe("1"); expect(closeCount2).toBe("2"); const failedAttemptResponse3 = getAuthResult(server, "11111111"); expect(failedAttemptResponse3.response.code).toBe(ResponseCodeEnum.AuthFailure); const [correctCount3, closeCount3] = getData(); expect(correctCount3).toBe("2"); expect(closeCount3).toBe("0"); const failedAttemptResponse4 = getAuthResult(server, "1122334"); expect(failedAttemptResponse4.response.code).toBe(ResponseCodeEnum.AuthFailure); const [correctCount4, closeCount4] = getData(); expect(correctCount4).toBe("6"); expect(closeCount4).toBe("1"); const failedAttemptResponse5 = getAuthResult(server, "22114333"); expect(failedAttemptResponse5.response.code).toBe(ResponseCodeEnum.AuthFailure); expect(server.hasAdminRights).toBe(false); const [correctCount5, closeCount5] = getData(); expect(correctCount5).toBe("2"); expect(closeCount5).toBe("6"); server.password = "2435"; const failedAttemptResponse6 = getAuthResult(server, "3423"); expect(failedAttemptResponse6.response.code).toBe(ResponseCodeEnum.AuthFailure); expect(server.hasAdminRights).toBe(false); const [correctCount6, closeCount6] = getData(); expect(correctCount6).toBe("1"); expect(closeCount6).toBe("2"); expect(getAuthResult(server, server.password, 1).result.code).toBe(ResponseCodeEnum.Success); expect(server.hasAdminRights).toBe(true); }); test("getConvertToBase10Server creates a server with a correct password hint", () => { const server = serverFactory(getConvertToBase10Config, 5, 0, 0); expect(server).toBeDefined(); const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1); expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure); if (!failedAttemptResponse.response.data) { throw new Error(`Password response does not contain data: ${JSON.stringify(failedAttemptResponse.response)}`); } assertString(failedAttemptResponse.response.data); const [base, numberString] = failedAttemptResponse.response.data.split(","); expect(numberString).toBe(encodeNumberInBaseN(+server.password, Number(base))); const attemptedPassword = Number.parseInt(numberString, Number(base)); const result = getAuthResult(server, `${attemptedPassword}`, 1); expect(isCloseToCorrectPassword(server.password, attemptedPassword, true)).toBe(true); expect(result.response.code).toBe(ResponseCodeEnum.Success); }); test("getConvertToBase10Server creates a server with a correct password hint, and has a non-integer solution at higher difficulties ", () => { const server = serverFactory(getConvertToBase10Config, 15, 0, 0); expect(server).toBeDefined(); const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1); expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure); if (!failedAttemptResponse.response.data) { throw new Error(`Password response does not contain data: ${JSON.stringify(failedAttemptResponse.response)}`); } assertString(failedAttemptResponse.response.data); const [base, numberString] = failedAttemptResponse.response.data.split(","); expect(numberString).toBe(encodeNumberInBaseN(+server.password, Number(base))); // custom parser is used to handle non-integer answers const attemptedPassword = parseBaseNNumberString(numberString, Number(base)); const result = getAuthResult(server, `${attemptedPassword}`, 1); expect(isCloseToCorrectPassword(server.password, attemptedPassword)).toBe(true); expect(result.response.code).toBe(ResponseCodeEnum.Success); }); test("encodeNumberInBaseN and parseBaseNNumberString encode/decode numbers correctly", () => { expect(encodeNumberInBaseN(15, 5.5)).toBe("24"); expect(encodeNumberInBaseN(16, 5.5)).toBe("25"); expect(encodeNumberInBaseN(17, 5.5)).toBe("30.24034"); expect(encodeNumberInBaseN(264, 17.6)).toBe("EH.A9F"); expect(parseBaseNNumberString("24", 5.5)).toBe(15); expect(parseBaseNNumberString("25", 5.5)).toBe(16); const aprox = parseBaseNNumberString("30.24", 5.5); expect(Math.abs(aprox - 17) < 0.1).toBe(true); expect(encodeNumberInBaseN(7, 2)).toBe("111"); expect(encodeNumberInBaseN(112, 2)).toBe("1110000"); }); test("parseSimpleArithmeticExpression parses expressions correctly", () => { expect(parseSimpleArithmeticExpression("1 + 2")).toBe(3); expect(parseSimpleArithmeticExpression("1 - 2")).toBe(-1); expect(parseSimpleArithmeticExpression("5 + 1 * 3")).toBe(8); expect(parseSimpleArithmeticExpression("5 * ( 6 + 7 )")).toBe(65); expect(parseSimpleArithmeticExpression("4 + 5 * ( 6 + 7 ) / 2")).toBe(36.5); expect(parseSimpleArithmeticExpression("1 + 3 * ( 4 / 5 ) / 2 + 4 ")).toBe(6.2); expect(Math.abs(parseSimpleArithmeticExpression("1 + 3 * ((4 / 5) / 2 ) * 3 + 4 ")) - 8.6 < 0.01).toBe(true); expect( Math.abs( parseSimpleArithmeticExpression( "23 * ( 41 + 76 + 32 * 27 * 6 ) - 34 - 49 + 93 - ( 11 / 41 - 62 / 6 + 5 ) * 19 - 0", ), ) - 122029.235 < 0.9, ).toBe(true); expect(parseSimpleArithmeticExpression("48 - 38 * 24 + ( 72 / 8 * 4 ) - 76 * 61 * 16")).toBe(-75004); expect( Math.ceil(parseSimpleArithmeticExpression("8 / 15 / 91 / ( 54 * 10 * 84 ) - 77 * 83 + ( 83 * 75 / 8 ) + 54")), ).toBe(-5558); expect( parseSimpleArithmeticExpression( "37 / 8 / 81 / ( 1 + ( 80 * 31 ) - 26 - 53 ) / 52 / ( 18 * 72 / 78 ) * 83 * ( 21 * 88 + 96 ) + 23", ), ).toBeCloseTo(24.61, -1); expect(parseSimpleArithmeticExpression("94 / ( 76 * 63 * ( 89 * 33 ) + 70 ) * 13 * 73 * 61 * 81 * 74")).toBeCloseTo( 2319.425, ); expect( parseSimpleArithmeticExpression( "94 / ( 76 * 63 * ( 89 * 33 ) + 70 ) * 13 * 73 * 61 * 81 * 74;alert('injection!')", ), ).toBeCloseTo(2319.425); const value = parseSimpleArithmeticExpression("76 + 30 * ( 3 * 10 + 14 ) - 83 / 47 + 16"); expect(isCloseToCorrectPassword(value.toString(), 1410.2340425531916)).toBe(true); const value2 = parseSimpleArithmeticExpression( " 5 ÷ ( 30 ➕ 64 ➕ 11 ) ҳ 98 ÷ 17 ➕ 20 ➕ 58 ➖ ( 64 ➖ 4 ҳ 80 ) ҳ 90", ); expect(isCloseToCorrectPassword(value2.toString(), 23118.274509803923)).toBe(true); const expression = generateSimpleArithmeticExpression(20); expect(eval(cleanArithmeticExpression(expression))).toBeCloseTo(parseSimpleArithmeticExpression(expression)); }); test("getParseArithmeticExpressionConfig server creates a server with a correct password hint", () => { const server = serverFactory(() => getParseArithmeticExpressionConfig(25), 25, 0, 0); expect(server).toBeDefined(); const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1); expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure); const expression = failedAttemptResponse.response.data; const attemptedPassword = parseSimpleArithmeticExpression(`${expression}`); const result = getAuthResult(server, `${attemptedPassword}`, 1); expect(isCloseToCorrectPassword(server.password, attemptedPassword, true)).toBe(true); expect(result.response.code).toBe(ResponseCodeEnum.Success); }); test("getBinaryEncodedConfig server creates a server with a correct password hint", () => { const server = serverFactory(() => getBinaryEncodedConfig(20), 20, 0, 0); expect(server).toBeDefined(); const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1); expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure); const binaryString = failedAttemptResponse.response.data; // convert each 8 bits to a character, then join const attemptedPassword = String.fromCharCode(...`${binaryString}`.split(" ").map((byte) => parseInt(byte, 2))); const result = getAuthResult(server, `${attemptedPassword}`, 1); expect(attemptedPassword).toBe(server.password); expect(result.response.code).toBe(ResponseCodeEnum.Success); }); test("getXorMaskEncryptedPasswordConfig server creates a server with a correct password hint", () => { const server = serverFactory(() => getXorMaskEncryptedPasswordConfig(), 20, 0, 0); expect(server).toBeDefined(); const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1); expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure); const xorData = failedAttemptResponse.response.data; if (!xorData) { throw new Error(`Password response does not contain data: ${JSON.stringify(failedAttemptResponse.response)}`); } assertString(xorData); const [encryptedPasswordString, maskString] = xorData.split(";"); const mask = maskString.split(" ").map((byte) => parseInt(byte, 2)); const encryptedPasswordChars = encryptedPasswordString.split(""); const attemptedPassword = encryptedPasswordChars .map((char, i) => String.fromCharCode(char.charCodeAt(0) ^ mask[i])) .join(""); const result = getAuthResult(server, `${attemptedPassword}`, 1); expect(attemptedPassword).toBe(server.password); expect(result.response.code).toBe(ResponseCodeEnum.Success); }); test("getTimingAttackConfig server creates a server that takes longer the more correct characters are submitted, starting from the left", async () => { Player.gainCharismaExp(1e200); const server = serverFactory(getTimingAttackConfig, 20, 0, 0); server.logTrafficInterval = -1; const ns = getNS(server.hostname); expect(server).toBeDefined(); const failedAttemptResponse = getAuthResult(server, "$$$$$$$", 1); expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure); const response1 = await ns.dnet.authenticate(server.hostname, server.password.slice(0, 1) + "%%%%", 0); const firstAuthTime = getLatestResponseTime(server); const response2 = await ns.dnet.authenticate(server.hostname, server.password.slice(0, 2) + "%%%", 0); const secondAuthTime = getLatestResponseTime(server); const response3 = await ns.dnet.authenticate(server.hostname, server.password.slice(0, 3) + "%%", 0); const thirdAuthTime = getLatestResponseTime(server); const response4WithWorseGuess = await ns.dnet.authenticate(server.hostname, "%%%%%%", 0); const badAuthTime = getLatestResponseTime(server); expect(badAuthTime).toBeLessThan(firstAuthTime); expect(secondAuthTime).toBeGreaterThan(firstAuthTime); expect(thirdAuthTime).toBeGreaterThan(secondAuthTime); expect(response1.code).toBe(ResponseCodeEnum.AuthFailure); expect(response2.code).toBe(ResponseCodeEnum.AuthFailure); expect(response3.code).toBe(ResponseCodeEnum.AuthFailure); expect(response4WithWorseGuess.code).toBe(ResponseCodeEnum.AuthFailure); expect(response1.message).toBe(GenericResponseMessage.AuthFailure); const serverLogs = getServerLogs(server, 5, true); expect(serverLogs.length).toBe(5); assertPasswordResponse(serverLogs[4].message); assertPasswordResponse(serverLogs[3].message); assertPasswordResponse(serverLogs[2].message); assertPasswordResponse(serverLogs[1].message); assertPasswordResponse(serverLogs[0].message); expect(serverLogs[4].message.message).toBe("Found a mismatch while checking each character (0)"); expect(serverLogs[3].message.message).toBe("Found a mismatch while checking each character (1)"); expect(serverLogs[2].message.message).toBe("Found a mismatch while checking each character (2)"); expect(serverLogs[1].message.message).toBe("Found a mismatch while checking each character (3)"); expect(serverLogs[0].message.message).toBe("Found a mismatch while checking each character (0)"); const successfulResponse = await ns.dnet.authenticate(server.hostname, server.password, 0); expect(successfulResponse.code).toBe(ResponseCodeEnum.Success); }); test("getTimingAttackConfig server only gives response time improvements on the exact correct character", () => { const server = serverFactory(getTimingAttackConfig, 20, 0, 0); server.logTrafficInterval = -1; expect(server).toBeDefined(); getAuthResult(server, server.password.slice(0, 3), 0); const baseAuthTime = getLatestResponseTime(server); expect(getServerLogs(server, 10).length).toBe(1); for (const char of [...lettersUppercase, ...lettersLowercase, ...numbers]) { if (char === server.password[1]) continue; getAuthResult(server, server.password.slice(0, 3) + char, 0); const testAuthTime = getLatestResponseTime(server); expect(testAuthTime).toBe(baseAuthTime); expect(getServerLogs(server, 10).length).toBe(1); } }); test("getSpiceLevelConfig server creates a server with a correct password hint", () => { const server = serverFactory(getSpiceLevelConfig, 20, 0, 0); expect(server).toBeDefined(); const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1); expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure); const correctChars = server.password.slice(0, 3); const result1 = getAuthResult(server, `${correctChars}%%%%%%`, 1); expect(result1.response.code).toBe(ResponseCodeEnum.AuthFailure); expect(result1.response.message).toBe("Not spicy enough"); expect(result1.response.data).toBe("🌶️🌶️🌶️/10"); const result2 = getAuthResult(server, `%%%%%%%%`, 1); expect(result2.response.code).toBe(ResponseCodeEnum.AuthFailure); expect(result2.response.message).toBe("Not spicy enough"); expect(result2.response.data).toBe("0/10"); const result3 = getAuthResult(server, `${correctChars.slice(0, 2)}%%%%`, 1); expect(result3.response.code).toBe(ResponseCodeEnum.AuthFailure); expect(result3.response.message).toBe("Not spicy enough"); expect(result3.response.data).toBe("🌶️🌶️/10"); const result4 = getAuthResult(server, server.password, 1); expect(result4.response.code).toBe(ResponseCodeEnum.Success); }); test("getGuessNumberConfig server creates a server with a correct password hint", () => { const server = serverFactory(getGuessNumberConfig, 20, 0, 0); expect(server).toBeDefined(); const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1); expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure); const response1 = getAuthResult(server, `${+server.password - 10}`, 1); expect(response1.response.code).toBe(ResponseCodeEnum.AuthFailure); expect(response1.response.data).toBe("Higher"); const response2 = getAuthResult(server, `${+server.password + 10}`, 1); expect(response2.response.code).toBe(ResponseCodeEnum.AuthFailure); expect(response2.response.data).toBe("Lower"); const response3 = getAuthResult(server, server.password, 1); expect(response3.response.code).toBe(ResponseCodeEnum.Success); }); test("getCaptchaConfig server creates a server with a correct password hint", () => { const server = serverFactory(getCaptchaConfig, 20, 0, 0); expect(server).toBeDefined(); const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1); expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure); const password = server.passwordHintData .split("") .filter((c) => !isNaN(+c)) .join(""); const result = getAuthResult(server, `${password}`, 1); expect(result.result.success).toBe(true); }); test("getYesn_tConfig server creates a server with a correct password hint", () => { const server = serverFactory(getYesn_tConfig, 20, 0, 0); expect(server).toBeDefined(); const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1); expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure); const result1 = getAuthResult(server, server.password.slice(0, 2) + "%%%%%", 1); expect(result1.response.code).toBe(ResponseCodeEnum.AuthFailure); expect(result1.response.data).toBe("yes,yes,yesn't,yesn't,yesn't,yesn't,yesn't"); const result2 = getAuthResult(server, `%%%${server.password.slice(3, 5)}%%%%%`, 1); expect(result2.response.code).toBe(ResponseCodeEnum.AuthFailure); expect(result2.response.data).toBe("yesn't,yesn't,yesn't,yes,yes,yesn't,yesn't,yesn't,yesn't,yesn't"); const result3 = getAuthResult(server, server.password, 1); expect(result3.response.code).toBe(ResponseCodeEnum.Success); }); test("getRomanNumeralsServer creates a server with a correct password hint", () => { const server = serverFactory(getRomanNumeralConfig, 5, 0, 0); expect(server).toBeDefined(); const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1); expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure); const attemptedPassword = romanNumeralDecoder(server.passwordHintData); const result = getAuthResult(server, `${attemptedPassword}`, 1); expect(result.result.success).toBe(true); expect(result.response.code).toBe(ResponseCodeEnum.Success); }); test("getLargestPrimeFactor server creates valid password and hint", () => { const server = serverFactory(getLargestPrimeFactorConfig, 20, 0, 0); const password = +server.password; const hint = +server.passwordHintData; expect(isNumber(password)).toBe(true); expect(isNumber(hint)).toBe(true); const factor = hint / password; expect(factor).toEqual(Math.floor(factor)); const factors = largePrimes .filter((n) => hint / n === Math.floor(hint / n)) .sort() .toReversed(); expect(factors[0]).toEqual(password); }); test("getDivisibilityTestConfig server creates valid password and hint", () => { const server = serverFactory(getDivisibilityTestConfig, 100, 0, 0); expect(server.password.includes("+")).toBe(false); expect(isNumber(+server.password)).toBe(true); expect(isNumber(+server.passwordHintData)).toBe(true); DarknetState.serverState.set(server.hostname, { serverLogs: [], authenticatedPIDs: [], }); const nonDivisibleResult = getAuthResult(server, `${server.password + 1}`, 1); expect(nonDivisibleResult.response.code).toBe(ResponseCodeEnum.AuthFailure); expect(nonDivisibleResult.response.message).toContain("not divisible"); const authLog = getMostRecentAuthLog(server.hostname); if (!authLog) { throw new Error(`Cannot find the most recent auth log in ${server.hostname}`); } expect(authLog.message).toContain("not divisible"); let factor = 2; while (+server.password % factor !== 0) { ++factor; } const divisibleResult = getAuthResult(server, `${factor}`, 1); expect(divisibleResult.response.code).toBe(ResponseCodeEnum.AuthFailure); expect(divisibleResult.response.message).toContain("IS divisible"); const correctResult = getAuthResult(server, `${server.password}`, 1); expect(correctResult.response.code).toBe(ResponseCodeEnum.Success); }); test("getTripleModuloConfig server creates valid password and hint", () => { const server = serverFactory(getTripleModuloConfig, 60, 0, 0); const guess1 = +server.password + 1; const expectedResult1 = (+server.password % guess1) % (((guess1 - 1) % 32) + 1); const result1 = getAuthResult(server, `${guess1}`, 1); expect(result1.response.code).toBe(ResponseCodeEnum.AuthFailure); expect(getMostRecentAuthLog(server.hostname)?.message).toContain(`= ${expectedResult1}`); expect(getMostRecentAuthLog(server.hostname)?.data).toBe(`${expectedResult1}`); const guess2 = +server.password - 1; const expectedResult2 = (+server.password % guess2) % (((guess2 - 1) % 32) + 1); const result2 = getAuthResult(server, `${guess2}`, 1); expect(result2.response.code).toBe(ResponseCodeEnum.AuthFailure); expect(getMostRecentAuthLog(server.hostname)?.message).toContain(`= ${expectedResult2}`); expect(getMostRecentAuthLog(server.hostname)?.data).toBe(`${expectedResult2}`); const guess3 = Math.floor(+server.password / 2); const expectedResult3 = (+server.password % guess3) % (((guess3 - 1) % 32) + 1); const result3 = getAuthResult(server, `${guess3}`, 1); expect(result3.response.code).toBe(ResponseCodeEnum.AuthFailure); expect(getMostRecentAuthLog(server.hostname)?.message).toContain(`= ${expectedResult3}`); expect(getMostRecentAuthLog(server.hostname)?.data).toBe(`${expectedResult3}`); // Check a known factor server.password = (BigInt(server.password) * BigInt(2)).toString(); const guessOfFactor = 2; const expectedResultOfFactor = 0; const resultOfFactor = getAuthResult(server, `${guessOfFactor}`, 1); expect(resultOfFactor.response.code).toBe(ResponseCodeEnum.AuthFailure); expect(getMostRecentAuthLog(server.hostname)?.message).toContain(`= ${expectedResultOfFactor}`); expect(getMostRecentAuthLog(server.hostname)?.data).toBe(`${expectedResultOfFactor}`); const correctResult = getAuthResult(server, `${server.password}`, 1); expect(correctResult.response.code).toBe(ResponseCodeEnum.Success); }); test("bufferOverflow server creates valid password and hint", () => { const bufferOverflowServer = serverFactory(getBufferOverflowConfig, 5, 0, 0); bufferOverflowServer.logTrafficInterval = -1; const passwordLength = bufferOverflowServer.password.length; const failedResult = getAuthResult(bufferOverflowServer, "1", 1); expect(failedResult.response.code).toBe(ResponseCodeEnum.AuthFailure); const authLog = getMostRecentAuthLog(bufferOverflowServer.hostname); if (!authLog) { throw new Error(`Cannot find the most recent auth log in ${bufferOverflowServer.hostname}`); } const received = "1".padEnd(passwordLength, "ˍ").slice(0, passwordLength); const expected = "■".repeat(passwordLength); expect(authLog.message).toBe(`auth failed: received '${received}', expected '${expected}'`); expect(authLog.passwordAttempted).toBe("1".padEnd(passwordLength, "ˍ").slice(0, passwordLength)); const successResult = getAuthResult(bufferOverflowServer, "A".repeat(passwordLength * 2), 1); expect(successResult.response.code).toBe(ResponseCodeEnum.Success); }); test("sortedEchoVuln creates a valid password and hint", () => { const sortedEchoVulnServer = serverFactory(getSortedEchoVulnConfig, 20, 0, 0); sortedEchoVulnServer.password = "12345"; sortedEchoVulnServer.passwordHintData = "41532"; expect(sortedEchoVulnServer).toBeDefined(); const failedAttemptResponse = getAuthResult(sortedEchoVulnServer, "23456", 1); expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure); const logs1 = getMostRecentAuthLog(sortedEchoVulnServer.hostname); expect(logs1?.data).toBe(`${sortedEchoVulnServer.passwordHintData}; RMS Deviation:1.000`); getAuthResult(sortedEchoVulnServer, "23579", 1); expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure); const logs2 = getMostRecentAuthLog(sortedEchoVulnServer.hostname); expect(logs2?.data).toBe(`${sortedEchoVulnServer.passwordHintData}; RMS Deviation:2.490`); getAuthResult(sortedEchoVulnServer, "12355", 1); expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure); const logs3 = getMostRecentAuthLog(sortedEchoVulnServer.hostname); expect(logs3?.data).toBe(`${sortedEchoVulnServer.passwordHintData}; RMS Deviation:0.447`); expect(getAuthResult(sortedEchoVulnServer, sortedEchoVulnServer.password, 1).result.code).toBe( ResponseCodeEnum.Success, ); expect(sortedEchoVulnServer.hasAdminRights).toBe(true); }); test("kingOfTheHill server creates a valid password and hint", () => { const kingOfTheHillServer = serverFactory(getKingOfTheHillConfig, 60, 0, 0); const password = Number(kingOfTheHillServer.password); const result1 = getAuthResult(kingOfTheHillServer, `${password - 50}`, 1); expect(result1.response.code).toBe(ResponseCodeEnum.AuthFailure); const logs1 = getMostRecentAuthLog(kingOfTheHillServer.hostname); const result2 = getAuthResult(kingOfTheHillServer, `${password + 50}`, 1); expect(result2.response.code).toBe(ResponseCodeEnum.AuthFailure); const logs2 = getMostRecentAuthLog(kingOfTheHillServer.hostname); const resultNearPassword = getAuthResult(kingOfTheHillServer, `${password + 1}`, 1); expect(resultNearPassword.response.code).toBe(ResponseCodeEnum.AuthFailure); const logsNearPassword = getMostRecentAuthLog(kingOfTheHillServer.hostname); getAuthResult(kingOfTheHillServer, `${password - 1}`, 1); const logsNearPassword2 = getMostRecentAuthLog(kingOfTheHillServer.hostname); expect(Number(logs1?.data)).toBeLessThan(Number(logsNearPassword?.data)); expect(Number(logs2?.data)).toBeLessThan(Number(logsNearPassword?.data)); expect(Number(logsNearPassword?.data)).toBeLessThan(10000); expect(Number(logsNearPassword2?.data)).toBeLessThan(10000); getAuthResult(kingOfTheHillServer, `12345`, 1); const logs3 = getMostRecentAuthLog(kingOfTheHillServer.hostname); getAuthResult(kingOfTheHillServer, `12345`, 1); const logs4 = getMostRecentAuthLog(kingOfTheHillServer.hostname); expect(Number(logs3?.data)).toBe(Number(logs4?.data)); // For testing password spreads: poll the altitude and log the resulting graph // const results = []; // for (let attempt = password * 0.6; attempt <= password * 1.4; attempt += password/200) { // getAuthResult(kingOfTheHillServer, `${attempt}`, 1); // const data = getMostRecentAuthLog(kingOfTheHillServer.hostname)?.data; // results.push({ attempt, altitude: Number(data) }); // } // const graph = results.map(r => { // if (r.altitude > 0) { // return ".".repeat(Math.min(50, Math.floor(r.altitude / 200))) + "*"; // } // return "-".repeat(Math.min(50, Math.floor(-r.altitude / 200))); // }).join("\n"); // // console.log(graph); const correctResult = getAuthResult(kingOfTheHillServer, `${password}`, 1); expect(correctResult.response.code).toBe(ResponseCodeEnum.Success); }); }); const serverSnapshot = () => getAllDarknetServers() .map((s) => ({ hostname: s.hostname, depth: s.depth, leftOffset: s.leftOffset })) .sort((a, b) => (a.hostname < b.hostname ? -1 : a.hostname === b.hostname ? 0 : 1)); describe("mutateDarknet and webstorm", () => { test("mutateDarknet", () => { const spiedExceptionAlert = jest.spyOn(exceptionAlertModule, "exceptionAlert"); const spiedConsoleError = jest.spyOn(console, "error").mockImplementation(); for (let i = 0; i < 5000; ++i) { mutateDarknet(); } expect(spiedExceptionAlert).not.toHaveBeenCalled(); expect(spiedConsoleError).not.toHaveBeenCalled(); }); test("webstorm", async () => { const spiedExceptionAlert = jest.spyOn(exceptionAlertModule, "exceptionAlert"); const spiedConsoleError = jest.spyOn(console, "error").mockImplementation(); jest.spyOn(UtilityModule, "sleep").mockImplementation(); for (let i = 0; i < 100; ++i) { await launchWebstorm(); } expect(spiedExceptionAlert).not.toHaveBeenCalled(); expect(spiedConsoleError).not.toHaveBeenCalled(); }); test("mutation during webstorm", async () => { const realRandom = Math.random; try { jest.useFakeTimers(); const promise = launchWebstorm(); expect(DarknetState.mutationLock).toBeTruthy(); await jest.advanceTimersByTimeAsync(10000); expect(DarknetState.mutationLock).toBeTruthy(); const initialServers = serverSnapshot(); // Low rolls cause stuff to happen, we want deterministic testing. // Jest spies keep state, make our own mock so it doesn't eat memory in // case something goes wrong. let count = 0; Math.random = () => count++ * (Number.EPSILON * 1024); mutateDarknet(); expect(serverSnapshot()).toEqual(initialServers); await jest.runAllTimersAsync(); await promise; // Should immediately finish expect(DarknetState.mutationLock).toBeNull(); mutateDarknet(); expect(serverSnapshot()).not.toEqual(initialServers); } finally { jest.useRealTimers(); Math.random = realRandom; } }); test("prestige during webstorm", async () => { try { jest.useFakeTimers(); const promise = launchWebstorm(); await jest.advanceTimersByTimeAsync(0); // Finish any promises expect(DarknetState.mutationLock).toBeTruthy(); const beforePrestige = serverSnapshot(); prestigeAugmentation(); expect(DarknetState.mutationLock).toBeNull(); const initialServers = serverSnapshot(); // Validate that prestige changed the network expect(initialServers).not.toEqual(beforePrestige); await jest.runAllTimersAsync(); await promise; // Should immediately finish expect(DarknetState.mutationLock).toBeNull(); // Webstorm should not have changed anything expect(serverSnapshot()).toEqual(initialServers); } finally { jest.useRealTimers(); } }); }); function validatePath(hostname: string): void { expectWithMessage(isDirectoryPath(`${hostname}/`), true, `Invalid hostname: ${hostname}`); expectWithMessage(isFilePath(`${hostname}/data.txt`), true, `Invalid hostname: ${hostname}`); } describe("Darknet server name generator", () => { test("Base name and l33t", () => { for (const name of commonPasswordDictionary) { validatePath(name); } for (const name of loreNames) { validatePath(name); } for (const name of presetNames) { validatePath(name); } for (const prefix of ServerNamePrefixes) { for (const connector of connectors) { for (const suffix of ServerNameSuffixes) { validatePath(`${prefix}${connector}${suffix}`); } } } for (const name of Object.values(l33t)) { validatePath(name); } }); test("generateDarknetServerName", () => { DarknetState.offlineServers = new Set(); for (let i = 0; i < 1000; ++i) { validatePath(generateDarknetServerName()); } }); }); describe("Cache filename generator", () => { test("Random prefix", () => { for (let i = 0; i < 10000; ++i) { const cacheFilename = generateCacheFilename(false); if (!cacheFilename) { throw new Error("Invalid cache filename"); } validatePath(cacheFilename); } }); test("Cache file in labyrinth server", () => { const cacheFilename = generateCacheFilename(false, LAB_CACHE_NAME); if (!cacheFilename) { throw new Error("Invalid cache filename"); } validatePath(cacheFilename); }); }); describe("Clue filename generator", () => { test("getClueFileName", () => { for (let i = 0; i < 10000; ++i) { expect(() => { getClueFileName(passwordFileNames); getClueFileName(notebookFileNames); }).not.toThrow(); } }); });