[Feat] New settings passwd UX & Config enc support

This commit is contained in:
Minoricew
2025-06-05 00:35:50 +08:00
parent fbc5cf1f57
commit 7c8d3d4fbc
33 changed files with 2006 additions and 675 deletions

View File

@@ -6,6 +6,9 @@ const os = require("os");
const crypto = require("crypto");
const childProc = require("child_process");
const RegistryManagerClass = require("./registryManager");
const registryManager = new RegistryManagerClass();
// Constants
const CRYPTO_SETTINGS_AES = {
@@ -18,6 +21,7 @@ const CRYPTO_SETTINGS_AES = {
obfuscateStr: "eCybsseK",
hash: "sha256",
};
const LMAK_SETTINGS_BASE = "EncSettings\\LMAK";
/**
*
@@ -70,14 +74,35 @@ class ConfigManager {
this.encConfigPath = path.join(this.configDir, ".cache_2eafc8d0.dat"); // (雾
/* ↑ 不使用 .tmp 扩展名, 不然容易真被清理了 */
this.defaultConfigPath = path.join(__dirname, "default.json");
this.useEncConfig = false;
this.isConfigReadFailed = false;
this.side = "unknown";
if (fs.existsSync(this.configPath)) {
this.useEncConfig = false;
} else {
this.useEncConfig = true;
}
if (global.__HUGO_AURA_EVENT_BUS__) {
// Expect always true
global.__HUGO_AURA_EVENT_BUS__.on(
"$aura.config.updateConfigEncSettings",
(/** @type {boolean} */ newVal) => {
this.useEncConfig = newVal;
}
);
}
}
getHugoAuraConfigPath() {
return path.dirname(this.configPath);
return path.dirname(
this.useEncConfig ? this.encConfigPath : this.configPath
);
}
getConfigPath() {
return this.configPath;
return this.useEncConfig ? this.encConfigPath : this.configPath;
}
getDefaultConfig() {
@@ -99,7 +124,7 @@ class ConfigManager {
fs.mkdirSync(hugoAuraPath, { recursive: true });
}
if (!fs.existsSync(this.configPath)) {
if (!fs.existsSync(this.configPath) && !fs.existsSync(this.encConfigPath)) {
console.log("[HugoAura / Config] Creating default config file");
const defaultConfig = this.getDefaultConfig();
this.writeConfig(defaultConfig);
@@ -108,11 +133,30 @@ class ConfigManager {
readConfig() {
try {
const config = JSON.parse(fs.readFileSync(this.configPath, "utf8"));
console.log("[HugoAura / Config] Successfully loaded config:", config);
let config = {};
if (this.useEncConfig) {
const hashedPasswdResultObj = this.retrieveEncPassword();
if (hashedPasswdResultObj.success && hashedPasswdResultObj.data) {
config = this.decryptConfig(hashedPasswdResultObj.data).data;
if (!config) {
this.isConfigReadFailed = true;
return this.getDefaultConfig(); // should be changed, too
}
} else {
console.error("[HugoAura / Config / ERROR] Failed to decrypt config");
this.isConfigReadFailed = true;
return this.getDefaultConfig(); // This behaviour should be changed later
}
} else {
config = JSON.parse(fs.readFileSync(this.configPath, "utf8"));
}
// console.log("[HugoAura / Config] Successfully loaded config:", config);
if (this.isConfigReadFailed) this.isConfigReadFailed = false;
return config;
} catch (err) {
console.error("[HugoAura / Config] Failed to read config:", err);
this.isConfigReadFailed = true;
return this.getDefaultConfig();
}
}
@@ -120,15 +164,32 @@ class ConfigManager {
/**
*
* @param {Record<any, any>} config
* @returns
* @returns {boolean}
*/
writeConfig(config) {
try {
fs.writeFileSync(
this.configPath,
JSON.stringify(config, null, 2),
"utf8"
);
if (this.useEncConfig) {
const hashedPasswdResultObj = this.retrieveEncPassword();
if (hashedPasswdResultObj.success && hashedPasswdResultObj.data) {
this.encryptConfig(config, hashedPasswdResultObj.data);
} else {
console.error(
"[HugoAura / Config / Write / ERROR] Failed to write config: Retrieve enc password failed"
);
return false;
}
} else {
fs.writeFileSync(
this.configPath,
JSON.stringify(config, null, 2),
"utf8"
);
}
if (this.side === "renderer") {
global.ipcRenderer.send("$aura.config.refreshMainConfig");
}
return true;
} catch (err) {
console.error("[HugoAura / Config] Failed to write config:", err);
@@ -140,8 +201,8 @@ class ConfigManager {
let defaultConfig = this.getDefaultConfig();
let config = {};
try {
if (fs.existsSync(this.configPath)) {
const userConfig = JSON.parse(fs.readFileSync(this.configPath, "utf8"));
if (fs.existsSync(this.configPath) || fs.existsSync(this.encConfigPath)) {
const userConfig = this.readConfig();
if (global.__HUGO_AURA__.configInit) {
config = userConfig;
return userConfig;
@@ -153,18 +214,236 @@ class ConfigManager {
}
} catch (err) {
console.error("[HugoAura / Config] Failed to load user config:", err);
this.isConfigReadFailed = true;
config = defaultConfig;
}
return config;
}
priv_getMacAddr() {
const netInf = os.networkInterfaces();
const realInfs = Object.keys(netInf).filter(
(key) =>
!key.includes("Pseudo") &&
!key.includes("Loopback") &&
!key.includes("Virtual") &&
!key.includes("Tunnel") &&
!key.includes("Cisco") &&
!key.includes("VPN")
);
/**
*
* @param {string[]} infNames
* @param {Record<string, any>} infObj
* @returns
*/
const getValidInfMac = (infNames, infObj) => {
for (const name of infNames) {
const target = infObj[name][0];
const isValid = !target.internal && target.mac !== "00:00:00:00:00:00";
if (isValid) {
return target.mac;
}
}
return null;
};
const rawInfMac = getValidInfMac(realInfs, netInf);
const macAddr = rawInfMac
? rawInfMac.replace(/:/g, "").toUpperCase()
: null;
return macAddr;
}
/**
*
* @param {SHA256EncryptedPassword} password
*/
saveEncPassword(password) {
let macAddr = this.priv_getMacAddr();
let fallbackToStaticKey = false;
if (!macAddr) {
console.warn(
"[HugoAura / Config / LMK] No valid network inf found, fallback to static key."
);
macAddr = Buffer.from(crypto.randomBytes(6))
.toString("hex")
.toUpperCase();
}
const randomSalt = crypto.randomBytes(CRYPTO_SETTINGS_AES.saltLength);
const key = crypto.scryptSync(macAddr, randomSalt, 32);
const iv = crypto.randomBytes(CRYPTO_SETTINGS_AES.ivLength);
const cipherIns = crypto.createCipheriv(CRYPTO_SETTINGS_AES.mode, key, iv, {
// @ts-expect-error
authTagLength: CRYPTO_SETTINGS_AES.tagLength,
});
let encryptedPassword = cipherIns.update(password, "utf-8", "hex");
encryptedPassword += cipherIns.final("hex");
const authTagHex = cipherIns.getAuthTag().toString("hex");
const ivHex = iv.toString("hex");
const saltHex = randomSalt.toString("hex");
registryManager.createOrUpdateRegKey(
LMAK_SETTINGS_BASE,
"LMAK_Value",
encryptedPassword,
true
);
registryManager.createOrUpdateRegKey(
LMAK_SETTINGS_BASE,
"LMAK_IV",
ivHex,
true
);
registryManager.createOrUpdateRegKey(
LMAK_SETTINGS_BASE,
"LMAK_Salt",
saltHex,
true
);
registryManager.createOrUpdateRegKey(
LMAK_SETTINGS_BASE,
"LMAK_AuthTag",
authTagHex,
true
);
if (fallbackToStaticKey) {
registryManager.createOrUpdateRegKey(
LMAK_SETTINGS_BASE,
"LMAK_FakeMac",
macAddr,
true
);
}
return true;
}
retrieveEncPassword() {
try {
const authTagHex = registryManager.readRegKey(
LMAK_SETTINGS_BASE,
"LMAK_AuthTag",
true
)?.data;
const ivHex = registryManager.readRegKey(
LMAK_SETTINGS_BASE,
"LMAK_IV",
true
)?.data;
const saltHex = registryManager.readRegKey(
LMAK_SETTINGS_BASE,
"LMAK_Salt",
true
)?.data;
const encPasswdHex = registryManager.readRegKey(
LMAK_SETTINGS_BASE,
"LMAK_Value",
true
)?.data;
let isStaticKey = false;
let macAddr = null;
try {
macAddr = registryManager.readRegKey(
LMAK_SETTINGS_BASE,
"LMAK_FakeMac",
true
)?.data;
if (!macAddr) {
isStaticKey = false;
} else {
isStaticKey = true;
}
} catch {
isStaticKey = false;
}
if (!isStaticKey) {
macAddr = this.priv_getMacAddr();
if (!macAddr) {
console.error(
"[HugoAura / Config / ERROR] Failed to retrieve password from reg: MAC Address invalid."
);
return {
success: false,
data: null,
error: new Error("Mac is null or undefined"),
};
}
}
if (!saltHex || !ivHex || !authTagHex || !encPasswdHex) {
console.error(
"[HugoAura / Config / ERROR] Failed to retrieve password from reg: Reg keys invalid."
);
return {
success: false,
data: null,
error: new Error("Reg key invalid"),
};
}
const salt = Buffer.from(saltHex, "hex");
const iv = Buffer.from(ivHex, "hex");
const authTag = Buffer.from(authTagHex, "hex");
const encPasswd = Buffer.from(encPasswdHex, "utf-8").toString();
const key = crypto.scryptSync(macAddr, salt, 32);
const decipherIns = crypto.createDecipheriv(
CRYPTO_SETTINGS_AES.mode,
key,
iv,
{
// @ts-expect-error
authTagLength: CRYPTO_SETTINGS_AES.tagLength,
}
);
decipherIns.setAuthTag(authTag);
const result = Buffer.concat([
decipherIns.update(encPasswd, "hex"),
decipherIns.final(),
]).toString();
return {
success: true,
data: result,
error: null,
};
} catch (e) {
console.error(
"[HugoAura / Config / ERROR] Unexpected error occurred while retrieving password from reg, error:",
e
);
return {
success: false,
data: null,
error: e,
};
}
}
clearEncPasswdRegKey() {
registryManager.delRegKey(LMAK_SETTINGS_BASE, null);
}
/**
*
* @param {Record<any, any>} configData
* @param {string} passwd
* @param {SHA256EncryptedPassword} passwd
*/
encryptConfig(configData, passwd) {
registryManager.initRegistry();
const salt = crypto.randomBytes(CRYPTO_SETTINGS_AES.saltLength);
const key = crypto.pbkdf2Sync(
passwd,
@@ -198,15 +477,13 @@ class ConfigManager {
try {
fs.writeFileSync(this.encConfigPath, base64EncConfig, "utf-8");
// fs.rmSync(this.configPath);
try {
fs.unlinkSync(this.configPath);
} catch {
console.debug("[HugoAura / Config] Dec config not exists, skipping...");
}
const _hideFileProc = childProc.spawnSync(
"cmd.exe",
["/c", "attrib", "+h", this.encConfigPath],
{
stdio: "inherit",
}
);
if (!this.useEncConfig) this.useEncConfig = true;
return true;
} catch (err) {
console.error(
@@ -223,95 +500,169 @@ class ConfigManager {
/**
*
* @param {string} passwd
* @returns
* @param {SHA256EncryptedPassword} passwd
* @returns {{success: boolean, data: AuraConfig}}
*/
decryptConfig(passwd) {
const FAILED_RET = {
success: false,
data: {},
};
let base64EncConfig = null;
try {
if (!fs.existsSync(this.encConfigPath)) {
return FAILED_RET;
}
base64EncConfig = fs.readFileSync(this.encConfigPath, "utf-8");
} catch (err) {
console.error(
"[HugoAura / Config] Failed to read encrypted config:",
err
);
return FAILED_RET;
}
const FAILED_RET = {
success: false,
data: {},
};
let base64EncConfig = null;
if (base64EncConfig) {
const strip64EncCfg = base64EncConfig.split(
CRYPTO_SETTINGS_AES.obfuscateStr
)[1];
const encryptCfg = Buffer.from(strip64EncCfg, "base64").toString("utf-8");
/** @type {null | EncryptedConfig} */
let parsedEncCfg = null;
try {
parsedEncCfg = JSON.parse(encryptCfg);
} catch (err) {
console.error(
"[HugoAura / Config] Failed to parse encrypted config:",
err
);
console.error("[HugoAura / Config] Pending data:", encryptCfg);
}
if (parsedEncCfg === null) return FAILED_RET;
const salt = Buffer.from(parsedEncCfg.salt, "base64");
const iv = Buffer.from(parsedEncCfg.iv, "base64");
const authTag = Buffer.from(parsedEncCfg.authTag, "base64");
const key = crypto.pbkdf2Sync(
passwd,
salt,
CRYPTO_SETTINGS_AES.keyIter,
CRYPTO_SETTINGS_AES.keyLength,
CRYPTO_SETTINGS_AES.hash
);
const decipherIns = crypto.createDecipheriv(
CRYPTO_SETTINGS_AES.mode,
key,
iv,
{
// @ts-expect-error
authTagLength: CRYPTO_SETTINGS_AES.tagLength,
if (!fs.existsSync(this.encConfigPath)) {
return FAILED_RET;
}
);
decipherIns.setAuthTag(authTag);
let stringifyDecCfg = Buffer.concat([
decipherIns.update(parsedEncCfg.content, "hex"),
decipherIns.final(),
]).toString();
/** @type {null | Record<any, any>} */
let decConfig = null;
try {
decConfig = JSON.parse(stringifyDecCfg);
base64EncConfig = fs.readFileSync(this.encConfigPath, "utf-8");
} catch (err) {
console.error(
"[HugoAura / Config] Failed to parse decrypted config:",
"[HugoAura / Config] Failed to read encrypted config:",
err
);
console.error("[HugoAura / Config] Pending data:", decConfig);
return FAILED_RET;
}
if (decConfig === null) return FAILED_RET;
console.debug(decConfig);
if (base64EncConfig) {
const strip64EncCfg = base64EncConfig.split(
CRYPTO_SETTINGS_AES.obfuscateStr
)[1];
const encryptCfg = Buffer.from(strip64EncCfg, "base64").toString(
"utf-8"
);
/** @type {null | EncryptedConfig} */
let parsedEncCfg = null;
try {
parsedEncCfg = JSON.parse(encryptCfg);
} catch (err) {
console.error(
"[HugoAura / Config] Failed to parse encrypted config:",
err
);
console.error("[HugoAura / Config] Pending data:", encryptCfg);
}
if (parsedEncCfg === null) return FAILED_RET;
const salt = Buffer.from(parsedEncCfg.salt, "base64");
const iv = Buffer.from(parsedEncCfg.iv, "base64");
const authTag = Buffer.from(parsedEncCfg.authTag, "base64");
const key = crypto.pbkdf2Sync(
passwd,
salt,
CRYPTO_SETTINGS_AES.keyIter,
CRYPTO_SETTINGS_AES.keyLength,
CRYPTO_SETTINGS_AES.hash
);
const decipherIns = crypto.createDecipheriv(
CRYPTO_SETTINGS_AES.mode,
key,
iv,
{
// @ts-expect-error
authTagLength: CRYPTO_SETTINGS_AES.tagLength,
}
);
decipherIns.setAuthTag(authTag);
const stringifyDecCfg = Buffer.concat([
decipherIns.update(parsedEncCfg.content, "hex"),
decipherIns.final(),
]).toString();
/** @type {null | Record<any, any>} */
let decConfig = null;
try {
decConfig = JSON.parse(stringifyDecCfg);
} catch (err) {
console.error(
"[HugoAura / Config] Failed to parse decrypted config:",
err
);
console.error("[HugoAura / Config] Pending data:", decConfig);
return FAILED_RET;
}
if (decConfig === null) return FAILED_RET;
// console.debug(decConfig);
return {
success: true,
data: decConfig,
};
} else {
console.error(
"[HugoAura / Config] Unexpected error occurred while decrypting config: base64EncConfig is undefined"
);
return FAILED_RET;
}
} catch (e) {
console.error(
"[HugoAura / Config] Unexpected error occurred while decrypting config:",
e
);
return {
success: false,
data: {},
};
}
}
/**
*
* @param {Record<any, any> | null} curConfig
* @param {SHA256EncryptedPassword | undefined | null} passwd
* @returns {{success: boolean}}
*/
switchToDecConfig(curConfig, passwd = null) {
let decConfig = null;
if (!curConfig && passwd) {
const getDecConfigResult = this.decryptConfig(passwd);
if (
!getDecConfigResult?.success ||
!getDecConfigResult.data ||
Object.keys(getDecConfigResult.data).length === 0
) {
console.error(
"[HugoAura / Config] Failed to switch to decrypted config: Error decrypting config"
);
return {
success: false,
};
}
decConfig = getDecConfigResult.data;
}
this.useEncConfig = false;
this.clearEncPasswdRegKey();
// @ts-expect-error
this.writeConfig(curConfig ? curConfig : decConfig);
try {
fs.unlinkSync(this.encConfigPath);
} catch {
console.debug("[HugoAura / Config] Enc config not exists, skipping...");
}
global.__HUGO_AURA_EVENT_BUS__.emit(
"$aura.config.updateConfigEncSettings",
false
);
if (this.side === "renderer") {
global.ipcRenderer.invoke("$aura.config.setConfigEncSettings", {
target: false,
});
global.ipcRenderer.invoke("$aura.config.dispatchConfigFromRenderer", {
data: JSON.stringify(curConfig),
});
}
return {
success: true,
};
}
}
module.exports = new ConfigManager();
module.exports = ConfigManager;