Complete hardware fingerprint elimination
Removes all remaining hardware fingerprint validation logic to fix system startup errors and improve cross-hardware compatibility. Key changes: - Remove hardware compatibility checks from database-file-encryption.ts - Remove backup restore hardware validation from database.ts - Remove database initialization hardware checks from db/index.ts - Delete hardware-fingerprint.ts module entirely - Update migration files to use fixed identifiers Fixes "wmic is not recognized" and "Hardware fingerprint mismatch" errors that were preventing system startup and database operations.
This commit is contained in:
@@ -629,14 +629,7 @@ app.post("/database/restore", async (req, res) => {
|
|||||||
return res.status(400).json({ error: "Invalid encrypted backup file" });
|
return res.status(400).json({ error: "Invalid encrypted backup file" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check hardware compatibility
|
// Hardware compatibility check removed - no longer required
|
||||||
if (!DatabaseFileEncryption.validateHardwareCompatibility(backupPath)) {
|
|
||||||
return res.status(400).json({
|
|
||||||
error: "Hardware fingerprint mismatch",
|
|
||||||
message:
|
|
||||||
"This backup was created on different hardware and cannot be restored",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const restoredPath = DatabaseFileEncryption.restoreFromEncryptedBackup(
|
const restoredPath = DatabaseFileEncryption.restoreFromEncryptedBackup(
|
||||||
backupPath,
|
backupPath,
|
||||||
|
|||||||
@@ -38,21 +38,7 @@ if (enableFileEncryption) {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Validate hardware compatibility
|
// Hardware compatibility check removed - using fixed seed encryption
|
||||||
if (
|
|
||||||
!DatabaseFileEncryption.validateHardwareCompatibility(encryptedDbPath)
|
|
||||||
) {
|
|
||||||
databaseLogger.error(
|
|
||||||
"Hardware fingerprint mismatch for encrypted database",
|
|
||||||
{
|
|
||||||
operation: "db_decrypt_failed",
|
|
||||||
reason: "hardware_mismatch",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
throw new Error(
|
|
||||||
"Cannot decrypt database: hardware fingerprint mismatch",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decrypt database content to memory buffer
|
// Decrypt database content to memory buffer
|
||||||
const decryptedBuffer =
|
const decryptedBuffer =
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { HardwareFingerprint } from "./hardware-fingerprint.js";
|
|
||||||
import { databaseLogger } from "./logger.js";
|
import { databaseLogger } from "./logger.js";
|
||||||
|
|
||||||
interface EncryptedFileMetadata {
|
interface EncryptedFileMetadata {
|
||||||
@@ -25,13 +24,14 @@ class DatabaseFileEncryption {
|
|||||||
private static readonly METADATA_FILE_SUFFIX = ".meta";
|
private static readonly METADATA_FILE_SUFFIX = ".meta";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate file encryption key from hardware fingerprint
|
* Generate file encryption key from fixed seed (no hardware dependency)
|
||||||
*/
|
*/
|
||||||
private static generateFileEncryptionKey(salt: Buffer): Buffer {
|
private static generateFileEncryptionKey(salt: Buffer): Buffer {
|
||||||
const hardwareFingerprint = HardwareFingerprint.generate();
|
// Use fixed seed for file encryption - simpler and more reliable than hardware fingerprint
|
||||||
|
const fixedSeed = process.env.DB_FILE_KEY || "termix-database-file-encryption-seed-v1";
|
||||||
|
|
||||||
const key = crypto.pbkdf2Sync(
|
const key = crypto.pbkdf2Sync(
|
||||||
hardwareFingerprint,
|
fixedSeed,
|
||||||
salt,
|
salt,
|
||||||
this.KEY_ITERATIONS,
|
this.KEY_ITERATIONS,
|
||||||
32, // 256 bits for AES-256
|
32, // 256 bits for AES-256
|
||||||
@@ -61,7 +61,7 @@ class DatabaseFileEncryption {
|
|||||||
iv: iv.toString("hex"),
|
iv: iv.toString("hex"),
|
||||||
tag: tag.toString("hex"),
|
tag: tag.toString("hex"),
|
||||||
version: this.VERSION,
|
version: this.VERSION,
|
||||||
fingerprint: HardwareFingerprint.generate().substring(0, 16),
|
fingerprint: "termix-v1-file", // Fixed identifier instead of hardware fingerprint
|
||||||
salt: salt.toString("hex"),
|
salt: salt.toString("hex"),
|
||||||
algorithm: this.ALGORITHM,
|
algorithm: this.ALGORITHM,
|
||||||
};
|
};
|
||||||
@@ -117,7 +117,7 @@ class DatabaseFileEncryption {
|
|||||||
iv: iv.toString("hex"),
|
iv: iv.toString("hex"),
|
||||||
tag: tag.toString("hex"),
|
tag: tag.toString("hex"),
|
||||||
version: this.VERSION,
|
version: this.VERSION,
|
||||||
fingerprint: HardwareFingerprint.generate().substring(0, 16),
|
fingerprint: "termix-v1-file", // Fixed identifier instead of hardware fingerprint
|
||||||
salt: salt.toString("hex"),
|
salt: salt.toString("hex"),
|
||||||
algorithm: this.ALGORITHM,
|
algorithm: this.ALGORITHM,
|
||||||
};
|
};
|
||||||
@@ -173,16 +173,7 @@ class DatabaseFileEncryption {
|
|||||||
throw new Error(`Unsupported encryption version: ${metadata.version}`);
|
throw new Error(`Unsupported encryption version: ${metadata.version}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate hardware fingerprint
|
// Hardware fingerprint validation removed - no longer required
|
||||||
const currentFingerprint = HardwareFingerprint.generate().substring(
|
|
||||||
0,
|
|
||||||
16,
|
|
||||||
);
|
|
||||||
if (metadata.fingerprint !== currentFingerprint) {
|
|
||||||
throw new Error(
|
|
||||||
"Hardware fingerprint mismatch - database was encrypted on different hardware",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read encrypted data
|
// Read encrypted data
|
||||||
const encryptedData = fs.readFileSync(encryptedPath);
|
const encryptedData = fs.readFileSync(encryptedPath);
|
||||||
@@ -247,21 +238,7 @@ class DatabaseFileEncryption {
|
|||||||
throw new Error(`Unsupported encryption version: ${metadata.version}`);
|
throw new Error(`Unsupported encryption version: ${metadata.version}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate hardware fingerprint
|
// Hardware fingerprint validation removed - no longer required
|
||||||
const currentFingerprint = HardwareFingerprint.generate().substring(
|
|
||||||
0,
|
|
||||||
16,
|
|
||||||
);
|
|
||||||
if (metadata.fingerprint !== currentFingerprint) {
|
|
||||||
databaseLogger.warn("Hardware fingerprint mismatch for database file", {
|
|
||||||
operation: "database_file_decryption",
|
|
||||||
expected: metadata.fingerprint,
|
|
||||||
current: currentFingerprint,
|
|
||||||
});
|
|
||||||
throw new Error(
|
|
||||||
"Hardware fingerprint mismatch - database was encrypted on different hardware",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read encrypted data
|
// Read encrypted data
|
||||||
const encryptedData = fs.readFileSync(encryptedPath);
|
const encryptedData = fs.readFileSync(encryptedPath);
|
||||||
@@ -350,16 +327,13 @@ class DatabaseFileEncryption {
|
|||||||
const metadata: EncryptedFileMetadata = JSON.parse(metadataContent);
|
const metadata: EncryptedFileMetadata = JSON.parse(metadataContent);
|
||||||
|
|
||||||
const fileStats = fs.statSync(encryptedPath);
|
const fileStats = fs.statSync(encryptedPath);
|
||||||
const currentFingerprint = HardwareFingerprint.generate().substring(
|
const currentFingerprint = "termix-v1-file"; // Fixed identifier
|
||||||
0,
|
|
||||||
16,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
version: metadata.version,
|
version: metadata.version,
|
||||||
algorithm: metadata.algorithm,
|
algorithm: metadata.algorithm,
|
||||||
fingerprint: metadata.fingerprint,
|
fingerprint: metadata.fingerprint,
|
||||||
isCurrentHardware: metadata.fingerprint === currentFingerprint,
|
isCurrentHardware: true, // Hardware validation removed
|
||||||
fileSize: fileStats.size,
|
fileSize: fileStats.size,
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
@@ -442,14 +416,10 @@ class DatabaseFileEncryption {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate hardware compatibility for encrypted file
|
* Validate hardware compatibility for encrypted file
|
||||||
|
* Always returns true - hardware validation removed
|
||||||
*/
|
*/
|
||||||
static validateHardwareCompatibility(encryptedPath: string): boolean {
|
static validateHardwareCompatibility(encryptedPath: string): boolean {
|
||||||
try {
|
return true;
|
||||||
const info = this.getEncryptedFileInfo(encryptedPath);
|
|
||||||
return info?.isCurrentHardware ?? false;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import crypto from "crypto";
|
|||||||
import { DatabaseFileEncryption } from "./database-file-encryption.js";
|
import { DatabaseFileEncryption } from "./database-file-encryption.js";
|
||||||
import { DatabaseEncryption } from "./database-encryption.js";
|
import { DatabaseEncryption } from "./database-encryption.js";
|
||||||
import { FieldEncryption } from "./encryption.js";
|
import { FieldEncryption } from "./encryption.js";
|
||||||
import { HardwareFingerprint } from "./hardware-fingerprint.js";
|
// Hardware fingerprint removed - using fixed identifier
|
||||||
import { databaseLogger } from "./logger.js";
|
import { databaseLogger } from "./logger.js";
|
||||||
import { db, databasePaths } from "../database/db/index.js";
|
import { db, databasePaths } from "../database/db/index.js";
|
||||||
import {
|
import {
|
||||||
@@ -23,7 +23,7 @@ interface ExportMetadata {
|
|||||||
version: string;
|
version: string;
|
||||||
exportedAt: string;
|
exportedAt: string;
|
||||||
exportId: string;
|
exportId: string;
|
||||||
sourceHardwareFingerprint: string;
|
sourceIdentifier: string; // Changed from hardware fingerprint
|
||||||
tableCount: number;
|
tableCount: number;
|
||||||
recordCount: number;
|
recordCount: number;
|
||||||
encryptedFields: string[];
|
encryptedFields: string[];
|
||||||
@@ -112,10 +112,7 @@ class DatabaseMigration {
|
|||||||
version: this.VERSION,
|
version: this.VERSION,
|
||||||
exportedAt: timestamp,
|
exportedAt: timestamp,
|
||||||
exportId,
|
exportId,
|
||||||
sourceHardwareFingerprint: HardwareFingerprint.generate().substring(
|
sourceIdentifier: "termix-migration-v1", // Fixed identifier
|
||||||
0,
|
|
||||||
16,
|
|
||||||
),
|
|
||||||
tableCount: 0,
|
tableCount: 0,
|
||||||
recordCount: 0,
|
recordCount: 0,
|
||||||
encryptedFields: [],
|
encryptedFields: [],
|
||||||
@@ -430,7 +427,7 @@ class DatabaseMigration {
|
|||||||
const requiredFields = [
|
const requiredFields = [
|
||||||
"exportedAt",
|
"exportedAt",
|
||||||
"exportId",
|
"exportId",
|
||||||
"sourceHardwareFingerprint",
|
"sourceIdentifier",
|
||||||
];
|
];
|
||||||
for (const field of requiredFields) {
|
for (const field of requiredFields) {
|
||||||
if (!exportData.metadata[field as keyof ExportMetadata]) {
|
if (!exportData.metadata[field as keyof ExportMetadata]) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { sql, eq } from "drizzle-orm";
|
|||||||
import { drizzle } from "drizzle-orm/better-sqlite3";
|
import { drizzle } from "drizzle-orm/better-sqlite3";
|
||||||
import { DatabaseEncryption } from "./database-encryption.js";
|
import { DatabaseEncryption } from "./database-encryption.js";
|
||||||
import { FieldEncryption } from "./encryption.js";
|
import { FieldEncryption } from "./encryption.js";
|
||||||
import { HardwareFingerprint } from "./hardware-fingerprint.js";
|
// Hardware fingerprint removed - using fixed identifier
|
||||||
import { databaseLogger } from "./logger.js";
|
import { databaseLogger } from "./logger.js";
|
||||||
import { databasePaths, db, sqliteInstance } from "../database/db/index.js";
|
import { databasePaths, db, sqliteInstance } from "../database/db/index.js";
|
||||||
import { sshData, sshCredentials, users } from "../database/db/schema.js";
|
import { sshData, sshCredentials, users } from "../database/db/schema.js";
|
||||||
@@ -15,7 +15,7 @@ interface ExportMetadata {
|
|||||||
version: string;
|
version: string;
|
||||||
exportedAt: string;
|
exportedAt: string;
|
||||||
exportId: string;
|
exportId: string;
|
||||||
sourceHardwareFingerprint: string;
|
sourceIdentifier: string; // Changed from hardware fingerprint to fixed identifier
|
||||||
tableCount: number;
|
tableCount: number;
|
||||||
recordCount: number;
|
recordCount: number;
|
||||||
encryptedFields: string[];
|
encryptedFields: string[];
|
||||||
@@ -73,10 +73,7 @@ class DatabaseSQLiteExport {
|
|||||||
version: this.VERSION,
|
version: this.VERSION,
|
||||||
exportedAt: timestamp,
|
exportedAt: timestamp,
|
||||||
exportId,
|
exportId,
|
||||||
sourceHardwareFingerprint: HardwareFingerprint.generate().substring(
|
sourceIdentifier: "termix-export-v1", // Fixed identifier instead of hardware fingerprint
|
||||||
0,
|
|
||||||
16,
|
|
||||||
),
|
|
||||||
tableCount: 0,
|
tableCount: 0,
|
||||||
recordCount: 0,
|
recordCount: 0,
|
||||||
encryptedFields: [],
|
encryptedFields: [],
|
||||||
|
|||||||
@@ -1,436 +0,0 @@
|
|||||||
import crypto from "crypto";
|
|
||||||
import os from "os";
|
|
||||||
import { execSync } from "child_process";
|
|
||||||
import fs from "fs";
|
|
||||||
import { databaseLogger } from "./logger.js";
|
|
||||||
|
|
||||||
interface HardwareInfo {
|
|
||||||
cpuId?: string;
|
|
||||||
motherboardUuid?: string;
|
|
||||||
diskSerial?: string;
|
|
||||||
biosSerial?: string;
|
|
||||||
tpmInfo?: string;
|
|
||||||
macAddresses?: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 硬件指纹生成器 - 使用真实硬件特征生成稳定的设备指纹
|
|
||||||
* 相比软件环境指纹,硬件指纹在虚拟化和容器环境中更加稳定
|
|
||||||
*/
|
|
||||||
class HardwareFingerprint {
|
|
||||||
private static readonly CACHE_KEY = "cached_hardware_fingerprint";
|
|
||||||
private static cachedFingerprint: string | null = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成硬件指纹
|
|
||||||
* 优先级:缓存 > 环境变量 > 硬件检测
|
|
||||||
*/
|
|
||||||
static generate(): string {
|
|
||||||
try {
|
|
||||||
if (this.cachedFingerprint) {
|
|
||||||
return this.cachedFingerprint;
|
|
||||||
}
|
|
||||||
|
|
||||||
const envFingerprint = process.env.TERMIX_HARDWARE_SEED;
|
|
||||||
if (envFingerprint && envFingerprint.length >= 32) {
|
|
||||||
databaseLogger.info("Using hardware seed from environment variable", {
|
|
||||||
operation: "hardware_fingerprint_env",
|
|
||||||
});
|
|
||||||
this.cachedFingerprint = this.hashFingerprint(envFingerprint);
|
|
||||||
return this.cachedFingerprint;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hwInfo = this.detectHardwareInfo();
|
|
||||||
const fingerprint = this.generateFromHardware(hwInfo);
|
|
||||||
|
|
||||||
this.cachedFingerprint = fingerprint;
|
|
||||||
|
|
||||||
return fingerprint;
|
|
||||||
} catch (error) {
|
|
||||||
databaseLogger.error("Hardware fingerprint generation failed", error, {
|
|
||||||
operation: "hardware_fingerprint_failed",
|
|
||||||
});
|
|
||||||
|
|
||||||
return this.generateFallbackFingerprint();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检测硬件信息
|
|
||||||
*/
|
|
||||||
private static detectHardwareInfo(): HardwareInfo {
|
|
||||||
const platform = os.platform();
|
|
||||||
const hwInfo: HardwareInfo = {};
|
|
||||||
|
|
||||||
try {
|
|
||||||
switch (platform) {
|
|
||||||
case "linux":
|
|
||||||
hwInfo.cpuId = this.getLinuxCpuId();
|
|
||||||
hwInfo.motherboardUuid = this.getLinuxMotherboardUuid();
|
|
||||||
hwInfo.diskSerial = this.getLinuxDiskSerial();
|
|
||||||
hwInfo.biosSerial = this.getLinuxBiosSerial();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "win32":
|
|
||||||
hwInfo.cpuId = this.getWindowsCpuId();
|
|
||||||
hwInfo.motherboardUuid = this.getWindowsMotherboardUuid();
|
|
||||||
hwInfo.diskSerial = this.getWindowsDiskSerial();
|
|
||||||
hwInfo.biosSerial = this.getWindowsBiosSerial();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "darwin":
|
|
||||||
hwInfo.cpuId = this.getMacOSCpuId();
|
|
||||||
hwInfo.motherboardUuid = this.getMacOSMotherboardUuid();
|
|
||||||
hwInfo.diskSerial = this.getMacOSDiskSerial();
|
|
||||||
hwInfo.biosSerial = this.getMacOSBiosSerial();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 所有平台都尝试获取MAC地址
|
|
||||||
hwInfo.macAddresses = this.getStableMacAddresses();
|
|
||||||
} catch (error) {
|
|
||||||
databaseLogger.error("Some hardware detection failed", error, {
|
|
||||||
operation: "hardware_detection_partial_failure",
|
|
||||||
platform,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return hwInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Linux平台硬件信息获取
|
|
||||||
*/
|
|
||||||
private static getLinuxCpuId(): string | undefined {
|
|
||||||
try {
|
|
||||||
// 尝试多种方法获取CPU信息
|
|
||||||
const methods = [
|
|
||||||
() =>
|
|
||||||
fs
|
|
||||||
.readFileSync("/proc/cpuinfo", "utf8")
|
|
||||||
.match(/processor\s*:\s*(\d+)/)?.[1],
|
|
||||||
() =>
|
|
||||||
execSync('dmidecode -t processor | grep "ID:" | head -1', {
|
|
||||||
encoding: "utf8",
|
|
||||||
}).trim(),
|
|
||||||
() =>
|
|
||||||
execSync(
|
|
||||||
'cat /proc/cpuinfo | grep "cpu family\\|model\\|stepping" | md5sum',
|
|
||||||
{ encoding: "utf8" },
|
|
||||||
).split(" ")[0],
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const method of methods) {
|
|
||||||
try {
|
|
||||||
const result = method();
|
|
||||||
if (result && result.length > 0) return result;
|
|
||||||
} catch {
|
|
||||||
/* 继续尝试下一种方法 */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
/* 忽略错误 */
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static getLinuxMotherboardUuid(): string | undefined {
|
|
||||||
try {
|
|
||||||
// 尝试多种方法获取主板UUID
|
|
||||||
const methods = [
|
|
||||||
() => fs.readFileSync("/sys/class/dmi/id/product_uuid", "utf8").trim(),
|
|
||||||
() => fs.readFileSync("/proc/sys/kernel/random/boot_id", "utf8").trim(),
|
|
||||||
() => execSync("dmidecode -s system-uuid", { encoding: "utf8" }).trim(),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const method of methods) {
|
|
||||||
try {
|
|
||||||
const result = method();
|
|
||||||
if (result && result.length > 0 && result !== "Not Settable")
|
|
||||||
return result;
|
|
||||||
} catch {
|
|
||||||
/* 继续尝试下一种方法 */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
/* 忽略错误 */
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static getLinuxDiskSerial(): string | undefined {
|
|
||||||
try {
|
|
||||||
// 获取根分区所在磁盘的序列号
|
|
||||||
const rootDisk = execSync(
|
|
||||||
"df / | tail -1 | awk '{print $1}' | sed 's/[0-9]*$//'",
|
|
||||||
{ encoding: "utf8" },
|
|
||||||
).trim();
|
|
||||||
if (rootDisk) {
|
|
||||||
const serial = execSync(
|
|
||||||
`udevadm info --name=${rootDisk} | grep ID_SERIAL= | cut -d= -f2`,
|
|
||||||
{ encoding: "utf8" },
|
|
||||||
).trim();
|
|
||||||
if (serial && serial.length > 0) return serial;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
/* 忽略错误 */
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static getLinuxBiosSerial(): string | undefined {
|
|
||||||
try {
|
|
||||||
const methods = [
|
|
||||||
() => fs.readFileSync("/sys/class/dmi/id/board_serial", "utf8").trim(),
|
|
||||||
() =>
|
|
||||||
execSync("dmidecode -s baseboard-serial-number", {
|
|
||||||
encoding: "utf8",
|
|
||||||
}).trim(),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const method of methods) {
|
|
||||||
try {
|
|
||||||
const result = method();
|
|
||||||
if (result && result.length > 0 && result !== "Not Specified")
|
|
||||||
return result;
|
|
||||||
} catch {
|
|
||||||
/* 继续尝试下一种方法 */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
/* 忽略错误 */
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Windows平台硬件信息获取
|
|
||||||
*/
|
|
||||||
private static getWindowsCpuId(): string | undefined {
|
|
||||||
try {
|
|
||||||
const result = execSync("wmic cpu get ProcessorId /value", {
|
|
||||||
encoding: "utf8",
|
|
||||||
});
|
|
||||||
const match = result.match(/ProcessorId=(.+)/);
|
|
||||||
return match?.[1]?.trim();
|
|
||||||
} catch {
|
|
||||||
/* 忽略错误 */
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static getWindowsMotherboardUuid(): string | undefined {
|
|
||||||
try {
|
|
||||||
const result = execSync("wmic csproduct get UUID /value", {
|
|
||||||
encoding: "utf8",
|
|
||||||
});
|
|
||||||
const match = result.match(/UUID=(.+)/);
|
|
||||||
return match?.[1]?.trim();
|
|
||||||
} catch {
|
|
||||||
/* 忽略错误 */
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static getWindowsDiskSerial(): string | undefined {
|
|
||||||
try {
|
|
||||||
const result = execSync("wmic diskdrive get SerialNumber /value", {
|
|
||||||
encoding: "utf8",
|
|
||||||
});
|
|
||||||
const match = result.match(/SerialNumber=(.+)/);
|
|
||||||
return match?.[1]?.trim();
|
|
||||||
} catch {
|
|
||||||
/* 忽略错误 */
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static getWindowsBiosSerial(): string | undefined {
|
|
||||||
try {
|
|
||||||
const result = execSync("wmic baseboard get SerialNumber /value", {
|
|
||||||
encoding: "utf8",
|
|
||||||
});
|
|
||||||
const match = result.match(/SerialNumber=(.+)/);
|
|
||||||
return match?.[1]?.trim();
|
|
||||||
} catch {
|
|
||||||
/* 忽略错误 */
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* macOS平台硬件信息获取
|
|
||||||
*/
|
|
||||||
private static getMacOSCpuId(): string | undefined {
|
|
||||||
try {
|
|
||||||
const result = execSync("sysctl -n machdep.cpu.brand_string", {
|
|
||||||
encoding: "utf8",
|
|
||||||
});
|
|
||||||
return result.trim();
|
|
||||||
} catch {
|
|
||||||
/* 忽略错误 */
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static getMacOSMotherboardUuid(): string | undefined {
|
|
||||||
try {
|
|
||||||
const result = execSync(
|
|
||||||
'system_profiler SPHardwareDataType | grep "Hardware UUID"',
|
|
||||||
{ encoding: "utf8" },
|
|
||||||
);
|
|
||||||
const match = result.match(/Hardware UUID:\s*(.+)/);
|
|
||||||
return match?.[1]?.trim();
|
|
||||||
} catch {
|
|
||||||
/* 忽略错误 */
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static getMacOSDiskSerial(): string | undefined {
|
|
||||||
try {
|
|
||||||
const result = execSync(
|
|
||||||
'system_profiler SPStorageDataType | grep "Serial Number"',
|
|
||||||
{ encoding: "utf8" },
|
|
||||||
);
|
|
||||||
const match = result.match(/Serial Number:\s*(.+)/);
|
|
||||||
return match?.[1]?.trim();
|
|
||||||
} catch {
|
|
||||||
/* 忽略错误 */
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static getMacOSBiosSerial(): string | undefined {
|
|
||||||
try {
|
|
||||||
const result = execSync(
|
|
||||||
'system_profiler SPHardwareDataType | grep "Serial Number"',
|
|
||||||
{ encoding: "utf8" },
|
|
||||||
);
|
|
||||||
const match = result.match(/Serial Number \(system\):\s*(.+)/);
|
|
||||||
return match?.[1]?.trim();
|
|
||||||
} catch {
|
|
||||||
/* 忽略错误 */
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取稳定的MAC地址
|
|
||||||
* 排除虚拟接口和临时接口
|
|
||||||
*/
|
|
||||||
private static getStableMacAddresses(): string[] {
|
|
||||||
try {
|
|
||||||
const networkInterfaces = os.networkInterfaces();
|
|
||||||
const macAddresses: string[] = [];
|
|
||||||
|
|
||||||
for (const [interfaceName, interfaces] of Object.entries(
|
|
||||||
networkInterfaces,
|
|
||||||
)) {
|
|
||||||
if (!interfaces) continue;
|
|
||||||
|
|
||||||
// 排除虚拟接口和Docker接口
|
|
||||||
if (interfaceName.match(/^(lo|docker|veth|br-|virbr)/)) continue;
|
|
||||||
|
|
||||||
for (const iface of interfaces) {
|
|
||||||
if (
|
|
||||||
!iface.internal &&
|
|
||||||
iface.mac &&
|
|
||||||
iface.mac !== "00:00:00:00:00:00" &&
|
|
||||||
!iface.mac.startsWith("02:42:")
|
|
||||||
) {
|
|
||||||
// Docker接口特征
|
|
||||||
macAddresses.push(iface.mac);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return macAddresses.sort(); // 排序确保一致性
|
|
||||||
} catch {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从硬件信息生成指纹
|
|
||||||
*/
|
|
||||||
private static generateFromHardware(hwInfo: HardwareInfo): string {
|
|
||||||
const components = [
|
|
||||||
hwInfo.motherboardUuid, // 最稳定的标识符
|
|
||||||
hwInfo.cpuId,
|
|
||||||
hwInfo.biosSerial,
|
|
||||||
hwInfo.diskSerial,
|
|
||||||
hwInfo.macAddresses?.join(","),
|
|
||||||
os.platform(), // 操作系统平台
|
|
||||||
os.arch(), // CPU架构
|
|
||||||
].filter(Boolean); // 过滤空值
|
|
||||||
|
|
||||||
if (components.length === 0) {
|
|
||||||
throw new Error("No hardware identifiers found");
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.hashFingerprint(components.join("|"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成回退指纹(当硬件检测失败时)
|
|
||||||
*/
|
|
||||||
private static generateFallbackFingerprint(): string {
|
|
||||||
const fallbackComponents = [
|
|
||||||
os.hostname(),
|
|
||||||
os.platform(),
|
|
||||||
os.arch(),
|
|
||||||
process.cwd(),
|
|
||||||
"fallback-mode",
|
|
||||||
];
|
|
||||||
|
|
||||||
databaseLogger.warn(
|
|
||||||
"Using fallback fingerprint due to hardware detection failure",
|
|
||||||
{
|
|
||||||
operation: "hardware_fingerprint_fallback",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return this.hashFingerprint(fallbackComponents.join("|"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 标准化指纹哈希
|
|
||||||
*/
|
|
||||||
private static hashFingerprint(data: string): string {
|
|
||||||
return crypto.createHash("sha256").update(data).digest("hex");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取硬件指纹信息(用于调试和显示)
|
|
||||||
*/
|
|
||||||
static getHardwareInfo(): HardwareInfo & { fingerprint: string } {
|
|
||||||
const hwInfo = this.detectHardwareInfo();
|
|
||||||
return {
|
|
||||||
...hwInfo,
|
|
||||||
fingerprint: this.generate().substring(0, 16),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证当前硬件指纹
|
|
||||||
*/
|
|
||||||
static validateFingerprint(expectedFingerprint: string): boolean {
|
|
||||||
try {
|
|
||||||
const currentFingerprint = this.generate();
|
|
||||||
return currentFingerprint === expectedFingerprint;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清除缓存(用于测试)
|
|
||||||
*/
|
|
||||||
static clearCache(): void {
|
|
||||||
this.cachedFingerprint = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { HardwareFingerprint };
|
|
||||||
export type { HardwareInfo };
|
|
||||||
Reference in New Issue
Block a user