Cleanup files and improve file manager.
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import crypto from 'crypto';
|
||||
import { db } from '../database/db/index.js';
|
||||
import { settings } from '../database/db/schema.js';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { databaseLogger } from './logger.js';
|
||||
import { MasterKeyProtection } from './master-key-protection.js';
|
||||
import crypto from "crypto";
|
||||
import { db } from "../database/db/index.js";
|
||||
import { settings } from "../database/db/schema.js";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { databaseLogger } from "./logger.js";
|
||||
import { MasterKeyProtection } from "./master-key-protection.js";
|
||||
|
||||
interface EncryptionKeyInfo {
|
||||
hasKey: boolean;
|
||||
@@ -35,44 +35,49 @@ class EncryptionKeyManager {
|
||||
return MasterKeyProtection.decryptMasterKey(encodedKey);
|
||||
}
|
||||
|
||||
databaseLogger.warn('Found legacy base64-encoded key, migrating to KEK protection', {
|
||||
operation: 'key_migration_legacy'
|
||||
});
|
||||
const buffer = Buffer.from(encodedKey, 'base64');
|
||||
return buffer.toString('hex');
|
||||
databaseLogger.warn(
|
||||
"Found legacy base64-encoded key, migrating to KEK protection",
|
||||
{
|
||||
operation: "key_migration_legacy",
|
||||
},
|
||||
);
|
||||
const buffer = Buffer.from(encodedKey, "base64");
|
||||
return buffer.toString("hex");
|
||||
}
|
||||
|
||||
async initializeKey(): Promise<string> {
|
||||
databaseLogger.info('Initializing encryption key system...', {
|
||||
operation: 'key_init'
|
||||
});
|
||||
|
||||
try {
|
||||
let existingKey = await this.getStoredKey();
|
||||
|
||||
if (existingKey) {
|
||||
databaseLogger.success('Found existing encryption key', {
|
||||
operation: 'key_init',
|
||||
hasKey: true
|
||||
databaseLogger.success("Found existing encryption key", {
|
||||
operation: "key_init",
|
||||
hasKey: true,
|
||||
});
|
||||
this.currentKey = existingKey;
|
||||
return existingKey;
|
||||
}
|
||||
|
||||
const environmentKey = process.env.DB_ENCRYPTION_KEY;
|
||||
if (environmentKey && environmentKey !== 'default-key-change-me') {
|
||||
if (environmentKey && environmentKey !== "default-key-change-me") {
|
||||
if (!this.validateKeyStrength(environmentKey)) {
|
||||
databaseLogger.error('Environment encryption key is too weak', undefined, {
|
||||
operation: 'key_init',
|
||||
source: 'environment',
|
||||
keyLength: environmentKey.length
|
||||
});
|
||||
throw new Error('DB_ENCRYPTION_KEY is too weak. Must be at least 32 characters with good entropy.');
|
||||
databaseLogger.error(
|
||||
"Environment encryption key is too weak",
|
||||
undefined,
|
||||
{
|
||||
operation: "key_init",
|
||||
source: "environment",
|
||||
keyLength: environmentKey.length,
|
||||
},
|
||||
);
|
||||
throw new Error(
|
||||
"DB_ENCRYPTION_KEY is too weak. Must be at least 32 characters with good entropy.",
|
||||
);
|
||||
}
|
||||
|
||||
databaseLogger.info('Using encryption key from environment variable', {
|
||||
operation: 'key_init',
|
||||
source: 'environment'
|
||||
databaseLogger.info("Using encryption key from environment variable", {
|
||||
operation: "key_init",
|
||||
source: "environment",
|
||||
});
|
||||
|
||||
await this.storeKey(environmentKey);
|
||||
@@ -81,33 +86,35 @@ class EncryptionKeyManager {
|
||||
}
|
||||
|
||||
const newKey = await this.generateNewKey();
|
||||
databaseLogger.warn('Generated new encryption key - PLEASE BACKUP THIS KEY', {
|
||||
operation: 'key_init',
|
||||
generated: true,
|
||||
keyPreview: newKey.substring(0, 8) + '...'
|
||||
});
|
||||
databaseLogger.warn(
|
||||
"Generated new encryption key - PLEASE BACKUP THIS KEY",
|
||||
{
|
||||
operation: "key_init",
|
||||
generated: true,
|
||||
keyPreview: newKey.substring(0, 8) + "...",
|
||||
},
|
||||
);
|
||||
|
||||
return newKey;
|
||||
|
||||
} catch (error) {
|
||||
databaseLogger.error('Failed to initialize encryption key', error, {
|
||||
operation: 'key_init_failed'
|
||||
databaseLogger.error("Failed to initialize encryption key", error, {
|
||||
operation: "key_init_failed",
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async generateNewKey(): Promise<string> {
|
||||
const newKey = crypto.randomBytes(32).toString('hex');
|
||||
const keyId = crypto.randomBytes(8).toString('hex');
|
||||
const newKey = crypto.randomBytes(32).toString("hex");
|
||||
const keyId = crypto.randomBytes(8).toString("hex");
|
||||
|
||||
await this.storeKey(newKey, keyId);
|
||||
this.currentKey = newKey;
|
||||
|
||||
databaseLogger.success('Generated new encryption key', {
|
||||
operation: 'key_generated',
|
||||
databaseLogger.success("Generated new encryption key", {
|
||||
operation: "key_generated",
|
||||
keyId,
|
||||
keyLength: newKey.length
|
||||
keyLength: newKey.length,
|
||||
});
|
||||
|
||||
return newKey;
|
||||
@@ -115,41 +122,49 @@ class EncryptionKeyManager {
|
||||
|
||||
private async storeKey(key: string, keyId?: string): Promise<void> {
|
||||
const now = new Date().toISOString();
|
||||
const id = keyId || crypto.randomBytes(8).toString('hex');
|
||||
const id = keyId || crypto.randomBytes(8).toString("hex");
|
||||
|
||||
const keyData = {
|
||||
key: this.encodeKey(key),
|
||||
keyId: id,
|
||||
createdAt: now,
|
||||
algorithm: 'aes-256-gcm'
|
||||
algorithm: "aes-256-gcm",
|
||||
};
|
||||
|
||||
const encodedData = JSON.stringify(keyData);
|
||||
|
||||
try {
|
||||
const existing = await db.select().from(settings).where(eq(settings.key, 'db_encryption_key'));
|
||||
const existing = await db
|
||||
.select()
|
||||
.from(settings)
|
||||
.where(eq(settings.key, "db_encryption_key"));
|
||||
|
||||
if (existing.length > 0) {
|
||||
await db.update(settings)
|
||||
await db
|
||||
.update(settings)
|
||||
.set({ value: encodedData })
|
||||
.where(eq(settings.key, 'db_encryption_key'));
|
||||
.where(eq(settings.key, "db_encryption_key"));
|
||||
} else {
|
||||
await db.insert(settings).values({
|
||||
key: 'db_encryption_key',
|
||||
value: encodedData
|
||||
key: "db_encryption_key",
|
||||
value: encodedData,
|
||||
});
|
||||
}
|
||||
|
||||
const existingCreated = await db.select().from(settings).where(eq(settings.key, 'encryption_key_created'));
|
||||
const existingCreated = await db
|
||||
.select()
|
||||
.from(settings)
|
||||
.where(eq(settings.key, "encryption_key_created"));
|
||||
|
||||
if (existingCreated.length > 0) {
|
||||
await db.update(settings)
|
||||
await db
|
||||
.update(settings)
|
||||
.set({ value: now })
|
||||
.where(eq(settings.key, 'encryption_key_created'));
|
||||
.where(eq(settings.key, "encryption_key_created"));
|
||||
} else {
|
||||
await db.insert(settings).values({
|
||||
key: 'encryption_key_created',
|
||||
value: now
|
||||
key: "encryption_key_created",
|
||||
value: now,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -157,12 +172,11 @@ class EncryptionKeyManager {
|
||||
hasKey: true,
|
||||
keyId: id,
|
||||
createdAt: now,
|
||||
algorithm: 'aes-256-gcm'
|
||||
algorithm: "aes-256-gcm",
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
databaseLogger.error('Failed to store encryption key', error, {
|
||||
operation: 'key_store_failed'
|
||||
databaseLogger.error("Failed to store encryption key", error, {
|
||||
operation: "key_store_failed",
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
@@ -170,7 +184,10 @@ class EncryptionKeyManager {
|
||||
|
||||
private async getStoredKey(): Promise<string | null> {
|
||||
try {
|
||||
const result = await db.select().from(settings).where(eq(settings.key, 'db_encryption_key'));
|
||||
const result = await db
|
||||
.select()
|
||||
.from(settings)
|
||||
.where(eq(settings.key, "db_encryption_key"));
|
||||
|
||||
if (result.length === 0) {
|
||||
return null;
|
||||
@@ -182,34 +199,33 @@ class EncryptionKeyManager {
|
||||
try {
|
||||
keyData = JSON.parse(encodedData);
|
||||
} catch {
|
||||
databaseLogger.warn('Found legacy base64-encoded key data, migrating', {
|
||||
operation: 'key_data_migration_legacy'
|
||||
databaseLogger.warn("Found legacy base64-encoded key data, migrating", {
|
||||
operation: "key_data_migration_legacy",
|
||||
});
|
||||
keyData = JSON.parse(Buffer.from(encodedData, 'base64').toString());
|
||||
keyData = JSON.parse(Buffer.from(encodedData, "base64").toString());
|
||||
}
|
||||
|
||||
this.keyInfo = {
|
||||
hasKey: true,
|
||||
keyId: keyData.keyId,
|
||||
createdAt: keyData.createdAt,
|
||||
algorithm: keyData.algorithm
|
||||
algorithm: keyData.algorithm,
|
||||
};
|
||||
|
||||
const decodedKey = this.decodeKey(keyData.key);
|
||||
|
||||
if (!MasterKeyProtection.isProtectedKey(keyData.key)) {
|
||||
databaseLogger.info('Auto-migrating legacy key to KEK protection', {
|
||||
operation: 'key_auto_migration',
|
||||
keyId: keyData.keyId
|
||||
databaseLogger.info("Auto-migrating legacy key to KEK protection", {
|
||||
operation: "key_auto_migration",
|
||||
keyId: keyData.keyId,
|
||||
});
|
||||
await this.storeKey(decodedKey, keyData.keyId);
|
||||
}
|
||||
|
||||
return decodedKey;
|
||||
|
||||
} catch (error) {
|
||||
databaseLogger.error('Failed to retrieve stored encryption key', error, {
|
||||
operation: 'key_retrieve_failed'
|
||||
databaseLogger.error("Failed to retrieve stored encryption key", error, {
|
||||
operation: "key_retrieve_failed",
|
||||
});
|
||||
return null;
|
||||
}
|
||||
@@ -221,28 +237,31 @@ class EncryptionKeyManager {
|
||||
|
||||
async getKeyInfo(): Promise<EncryptionKeyInfo> {
|
||||
if (!this.keyInfo) {
|
||||
const hasKey = await this.getStoredKey() !== null;
|
||||
const hasKey = (await this.getStoredKey()) !== null;
|
||||
return {
|
||||
hasKey,
|
||||
algorithm: 'aes-256-gcm'
|
||||
algorithm: "aes-256-gcm",
|
||||
};
|
||||
}
|
||||
return this.keyInfo;
|
||||
}
|
||||
|
||||
async regenerateKey(): Promise<string> {
|
||||
databaseLogger.info('Regenerating encryption key', {
|
||||
operation: 'key_regenerate'
|
||||
databaseLogger.info("Regenerating encryption key", {
|
||||
operation: "key_regenerate",
|
||||
});
|
||||
|
||||
const oldKeyInfo = await this.getKeyInfo();
|
||||
const newKey = await this.generateNewKey();
|
||||
|
||||
databaseLogger.warn('Encryption key regenerated - ALL DATA MUST BE RE-ENCRYPTED', {
|
||||
operation: 'key_regenerated',
|
||||
oldKeyId: oldKeyInfo.keyId,
|
||||
newKeyId: this.keyInfo?.keyId
|
||||
});
|
||||
databaseLogger.warn(
|
||||
"Encryption key regenerated - ALL DATA MUST BE RE-ENCRYPTED",
|
||||
{
|
||||
operation: "key_regenerated",
|
||||
oldKeyId: oldKeyInfo.keyId,
|
||||
newKeyId: this.keyInfo?.keyId,
|
||||
},
|
||||
);
|
||||
|
||||
return newKey;
|
||||
}
|
||||
@@ -257,7 +276,11 @@ class EncryptionKeyManager {
|
||||
|
||||
const entropyTest = new Set(key).size / key.length;
|
||||
|
||||
const complexity = Number(hasLower) + Number(hasUpper) + Number(hasDigit) + Number(hasSpecial);
|
||||
const complexity =
|
||||
Number(hasLower) +
|
||||
Number(hasUpper) +
|
||||
Number(hasDigit) +
|
||||
Number(hasSpecial);
|
||||
return complexity >= 3 && entropyTest > 0.4;
|
||||
}
|
||||
|
||||
@@ -266,16 +289,20 @@ class EncryptionKeyManager {
|
||||
if (!testKey) return false;
|
||||
|
||||
try {
|
||||
const testData = 'validation-test-' + Date.now();
|
||||
const testBuffer = Buffer.from(testKey, 'hex');
|
||||
const testData = "validation-test-" + Date.now();
|
||||
const testBuffer = Buffer.from(testKey, "hex");
|
||||
|
||||
if (testBuffer.length !== 32) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const iv = crypto.randomBytes(16);
|
||||
const cipher = crypto.createCipheriv('aes-256-gcm', testBuffer, iv) as any;
|
||||
cipher.update(testData, 'utf8');
|
||||
const cipher = crypto.createCipheriv(
|
||||
"aes-256-gcm",
|
||||
testBuffer,
|
||||
iv,
|
||||
) as any;
|
||||
cipher.update(testData, "utf8");
|
||||
cipher.final();
|
||||
cipher.getAuthTag();
|
||||
|
||||
@@ -302,13 +329,16 @@ class EncryptionKeyManager {
|
||||
algorithm: keyInfo.algorithm,
|
||||
initialized: this.isInitialized(),
|
||||
kekProtected,
|
||||
kekValid: kekProtected ? MasterKeyProtection.validateProtection() : false
|
||||
kekValid: kekProtected ? MasterKeyProtection.validateProtection() : false,
|
||||
};
|
||||
}
|
||||
|
||||
private async isKEKProtected(): Promise<boolean> {
|
||||
try {
|
||||
const result = await db.select().from(settings).where(eq(settings.key, 'db_encryption_key'));
|
||||
const result = await db
|
||||
.select()
|
||||
.from(settings)
|
||||
.where(eq(settings.key, "db_encryption_key"));
|
||||
if (result.length === 0) return false;
|
||||
|
||||
const keyData = JSON.parse(result[0].value);
|
||||
@@ -320,4 +350,4 @@ class EncryptionKeyManager {
|
||||
}
|
||||
|
||||
export { EncryptionKeyManager };
|
||||
export type { EncryptionKeyInfo };
|
||||
export type { EncryptionKeyInfo };
|
||||
|
||||
Reference in New Issue
Block a user