mafiles
Version:
Parser for Steam Desktop Authenticator's (SDA) maFiles.
100 lines • 5.66 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const crypto_1 = __importDefault(require("crypto"));
const path_1 = __importDefault(require("path"));
const fs_1 = require("fs");
const utils_1 = require("./utils");
const JSONbig = require('json-bigint')({ useNativeBigInt: true });
/*
* Based on https://github.com/Jessecar96/SteamDesktopAuthenticator/blob/8a408f13ee24f70fffbc409cb0e050e924f4fe94/Steam%20Desktop%20Authenticator/FileEncryptor.cs#L20
*/
const PBKDF2_ITERATIONS = 50000; //Set to 50k to make program not unbearably slow. May increase in future.
const SALT_LENGTH = 8;
const KEY_SIZE_BYTES = 32;
const IV_LENGTH = 16;
/**
* Based on https://github.com/Jessecar96/SteamDesktopAuthenticator/blob/8a408f13ee24f70fffbc409cb0e050e924f4fe94/Steam%20Desktop%20Authenticator/FileEncryptor.cs#L62
*/
function getEncryptionKey(password, salt) {
return new Promise(resolve => {
crypto_1.default.pbkdf2(password, Buffer.from(salt, 'base64'), PBKDF2_ITERATIONS, KEY_SIZE_BYTES, 'sha1', ((err, derivedKey) => {
resolve(derivedKey);
}));
});
}
/**
* Based on https://github.com/Jessecar96/SteamDesktopAuthenticator/blob/8a408f13ee24f70fffbc409cb0e050e924f4fe94/Steam%20Desktop%20Authenticator/FileEncryptor.cs#L87
*
* @param password The encryption password.
* @param encryptionSalt `encryption_salt` from manifest.json
* @param encryptionIv `encryption_iv` from manifest.json
* @param encryptedData Content of the encrypted .maFile
*/
function decryptData(password, encryptionSalt, encryptionIv, encryptedData) {
return __awaiter(this, void 0, void 0, function* () {
const key = yield getEncryptionKey(password, encryptionSalt);
const decrypter = crypto_1.default.createDecipheriv("aes-256-cbc", key, Buffer.from(encryptionIv, 'base64'));
return decrypter.update(encryptedData, "base64", "utf8") + decrypter.final("utf8");
});
}
/**
* Attempts to read and parse a specified maFile. Supports decryption of the maFiles.
* If the maFile is encrypted and the password option isn't set, then the user will be prompted for the encryption password
* via STDIN.
*
* @param {string} manifestPath Path to the SDA manifest.json file containing more information about the maFiles.
* @param {(string|BigInt)} steamid64 steamid64 of the steam account that we want the maFile of.
* @param options All optional.
* @param {string} options.password The password that was used for encrypting the SDA. Define this for an instant decryption.
* @param {string} options.maFilePath This allows you to directly specify where the targeted maFile is located.
* @returns Either the JSON parsed maFile in case of a success or `null` in case of an error.
* @example
* const maFile = await mafiles("./SDA/manifest.json", "76561197960287930", {
* password: "3ncrypt10n_Passw0rd",
* maFilePath: "./SDA/maFiles/account.maFile"
* })
*/
function mafiles(manifestPath, steamid64, options = null) {
return __awaiter(this, void 0, void 0, function* () {
const manifest = JSONbig.parse((yield fs_1.promises.readFile(manifestPath)).toString());
if (typeof steamid64 === "string")
steamid64 = BigInt(steamid64);
else if (typeof steamid64 !== "bigint")
throw new Error(`The provided steamid64 ${steamid64} (${typeof steamid64}) is not a string nor a native BigInt.${typeof steamid64 === 'number' ? " The ID can't be used as a number due to JS limitations." : ""}`);
const entry = manifest.entries.find((entry) => entry.steamid === steamid64);
if (entry == null)
throw new Error(`Entry with the specified steamid64 ${steamid64} was not found in ${manifestPath}`);
const maFilePath = (options && options.maFilePath && options.maFilePath.length > 0) ? options.maFilePath : path_1.default.dirname(manifestPath) + `/${entry.filename}`;
const maFile = yield fs_1.promises.readFile(maFilePath);
if (manifest.encrypted) {
let password = null;
if (options && options.password)
password = options.password;
else {
let result = yield utils_1.readPromise({ prompt: 'Enter SDA encryption password:', silent: true });
password = result.result;
}
if (password == null || password.length <= 0 || typeof password !== "string")
throw new Error("The encryption password was not specified or is not of type string or has a length of 0");
return JSONbig.parse(yield decryptData(password, entry.encryption_salt, entry.encryption_iv, maFile.toString()));
}
else {
return JSONbig.parse(maFile.toString());
}
return null;
});
}
exports.default = mafiles;
//# sourceMappingURL=index.js.map