UNPKG

@alexstrnik/multifactor-cli

Version:

CLI tool for bypassing multifactor authentication

163 lines (162 loc) 5.85 kB
import { AsyncStorage, tcp } from "@aracna/core"; import { lstat, readFile, writeFile } from "fs/promises"; import { deserialize, serialize } from "v8"; import { join } from "path"; import { homedir } from "os"; import crypto from "crypto"; const PATH = join(homedir(), ".multifactor-cli"); const ENCRYPTION_ALGORITHM = "aes-256-cbc"; const IV_LENGTH = 16; const SALT_LENGTH = 16; const ENCRYPTION_MARKER = Buffer.from("ENCRYPTED_DATA_V1:"); function encryptData(data, password) { const salt = crypto.randomBytes(SALT_LENGTH); const key = crypto.scryptSync(password, salt, 32); const iv = crypto.randomBytes(IV_LENGTH); const cipher = crypto.createCipheriv(ENCRYPTION_ALGORITHM, key, iv); const encryptedData = Buffer.concat([cipher.update(data), cipher.final()]); return Buffer.concat([ENCRYPTION_MARKER, salt, iv, encryptedData]); } function decryptData(encryptedData, password) { try { if (!encryptedData .slice(0, ENCRYPTION_MARKER.length) .equals(ENCRYPTION_MARKER)) { return null; } let offset = ENCRYPTION_MARKER.length; const salt = encryptedData.slice(offset, offset + SALT_LENGTH); offset += SALT_LENGTH; const iv = encryptedData.slice(offset, offset + IV_LENGTH); offset += IV_LENGTH; const data = encryptedData.slice(offset); const key = crypto.scryptSync(password, salt, 32); const decipher = crypto.createDecipheriv(ENCRYPTION_ALGORITHM, key, iv); return Buffer.concat([decipher.update(data), decipher.final()]); } catch (error) { console.error("Decryption failed:", error); return null; } } async function readEncryptedFile(path, password) { try { const fileContent = await readFile(path); if (fileContent.length >= ENCRYPTION_MARKER.length && fileContent.slice(0, ENCRYPTION_MARKER.length).equals(ENCRYPTION_MARKER)) { const decryptedData = decryptData(fileContent, password); if (decryptedData) { return decryptedData; } throw new Error("Failed to decrypt file. Invalid password or corrupted file."); } return fileContent; } catch (error) { if (error.code === "ENOENT") { return Buffer.from(serialize({})); } throw error; } } async function writeEncryptedFile(path, data, password) { const encryptedData = encryptData(data, password); await writeFile(path, encryptedData); } export function SecureStorage(password) { return new AsyncStorage("SecureStorage", async () => { await writeEncryptedFile(PATH, serialize({}), password); }, async (key) => { let path, stat, file, json, item; path = PATH; stat = await tcp(() => lstat(path), false); if (stat instanceof Error) { await writeEncryptedFile(path, serialize({}), password); } file = await readEncryptedFile(path, password); try { json = deserialize(file); item = json[key]; if (item) { return item; } } catch (error) { console.error("Error deserializing data:", error); } return new Error(`The item does not exist.`); }, async (key) => { let path, stat, file, json; path = PATH; stat = await tcp(() => lstat(path), false); if (stat instanceof Error) { await writeEncryptedFile(path, serialize({}), password); } file = await readEncryptedFile(path, password); try { json = deserialize(file); return Boolean(json[key]); } catch (error) { console.error("Error deserializing data:", error); return false; } }, async (key) => { let path, stat, file, json; path = PATH; stat = await tcp(() => lstat(path), false); if (stat instanceof Error) { await writeEncryptedFile(path, serialize({}), password); } file = await readEncryptedFile(path, password); try { json = deserialize(file); if (json[key]) { delete json[key]; } return writeEncryptedFile(path, serialize(json), password); } catch (error) { console.error("Error deserializing data:", error); throw error; } }, async (key, item) => { let path, stat, file, json; path = PATH; stat = await tcp(() => lstat(path), false); if (stat instanceof Error) { await writeEncryptedFile(path, serialize({}), password); } file = await readEncryptedFile(path, password); try { json = deserialize(file); json[key] = item; return writeEncryptedFile(path, serialize(json), password); } catch (error) { console.error("Error deserializing data:", error); throw error; } }); } export async function isStorageEncrypted() { try { try { await lstat(PATH); } catch (error) { return { exists: false, encrypted: false }; } const fileContent = await readFile(PATH); const isEncrypted = fileContent.length >= ENCRYPTION_MARKER.length && fileContent.slice(0, ENCRYPTION_MARKER.length).equals(ENCRYPTION_MARKER); return { exists: true, encrypted: isEncrypted }; } catch (error) { console.error("Error checking storage encryption:", error); return { exists: false, encrypted: false }; } } export async function encryptStorage(password) { writeEncryptedFile(PATH, await readEncryptedFile(PATH, ""), password); }