diff --git a/SECURITY_REFACTOR_PLAN.md b/SECURITY_REFACTOR_PLAN.md deleted file mode 100644 index 55e621cc..00000000 --- a/SECURITY_REFACTOR_PLAN.md +++ /dev/null @@ -1,94 +0,0 @@ -# Termix 安全重构计划 - -## 现状分析 -- 当前所有密钥都用base64编码存储在数据库 -- JWT Secret和数据加密密钥混合管理 -- 没有真正的KEK-DEK分离 -- 数据库文件泄露 = 完全沦陷 - -## 目标架构 - -### 密钥层次 -``` -用户密码 → KEK → DEK → 字段加密密钥 → 数据 -系统启动 → JWT Secret → JWT Token → API认证 -``` - -### 存储分离 -``` -系统级:settings.system_jwt_secret (base64保护) -用户级:settings.user_kek_salt_${userId} -用户级:settings.user_encrypted_dek_${userId} (KEK保护) -``` - -## 修复步骤 - -### 第1步:新建分离的密钥管理类 -- [ ] 创建 SystemKeyManager (JWT密钥) -- [ ] 创建 UserKeyManager (用户数据密钥) -- [ ] 创建 SecuritySession (会话管理) - -### 第2步:重构认证流程 -- [ ] 修改用户注册:生成用户专属KEK salt和DEK -- [ ] 修改用户登录:验证密码 + 解锁数据密钥 -- [ ] 修改JWT验证:系统密钥验证 + 用户会话检查 - -### 第3步:重构数据加密 -- [ ] 分离数据加密和JWT密钥初始化 -- [ ] 修改EncryptedDBOperations使用用户会话密钥 -- [ ] 添加会话过期处理 - -### 第4步:数据库迁移 -- [ ] 创建迁移脚本:现有数据 → KEK保护 -- [ ] 向后兼容处理 -- [ ] 安全删除旧密钥 - -### 第5步:API修改 -- [ ] 添加用户密码验证接口 -- [ ] 修改所有加密相关接口 -- [ ] 添加会话管理接口 - -## 文件修改清单 - -### 新建文件 -- src/backend/utils/system-key-manager.ts -- src/backend/utils/user-key-manager.ts -- src/backend/utils/security-session.ts -- src/backend/utils/security-migration.ts - -### 修改文件 -- src/backend/utils/encryption-key-manager.ts (简化或删除) -- src/backend/utils/database-encryption.ts -- src/backend/utils/encrypted-db-operations.ts -- src/backend/database/routes/users.ts -- src/backend/database/database.ts - -### 数据库Schema -- 新增:user_kek_salt_${userId} -- 新增:user_encrypted_dek_${userId} -- 修改:system_jwt_secret (从current混合模式分离) - -## 安全考虑 - -### 密钥生命周期 -- JWT Secret: 应用生命周期 -- 用户KEK: 永不存储,从密码推导 -- 用户DEK: 会话期间,内存存储 -- 字段密钥: 临时推导,立即销毁 - -### 会话管理 -- 数据会话独立于JWT有效期 -- 非活跃自动过期 -- 用户登出立即清理 - -### 向后兼容 -- 检测旧格式数据 -- 用户登录时自动迁移 -- 迁移完成后删除旧密钥 - -## 测试计划 -- [ ] 密钥生成和推导测试 -- [ ] 加密解密正确性测试 -- [ ] 会话管理测试 -- [ ] 迁移流程测试 -- [ ] 性能影响评估 \ No newline at end of file diff --git a/src/backend/database/database.ts b/src/backend/database/database.ts index de856aab..5da60d4f 100644 --- a/src/backend/database/database.ts +++ b/src/backend/database/database.ts @@ -13,7 +13,6 @@ import "dotenv/config"; import { databaseLogger, apiLogger } from "../utils/logger.js"; import { SecuritySession } from "../utils/security-session.js"; import { DatabaseEncryption } from "../utils/database-encryption.js"; -import { SecurityMigration } from "../utils/security-migration.js"; import { DatabaseFileEncryption } from "../utils/database-file-encryption.js"; const app = express(); @@ -294,11 +293,9 @@ app.get("/encryption/status", async (req, res) => { try { const securitySession = SecuritySession.getInstance(); const securityStatus = await securitySession.getSecurityStatus(); - const migrationStatus = await SecurityMigration.checkMigrationStatus(); res.json({ security: securityStatus, - migration: migrationStatus, version: "v2-kek-dek", }); } catch (error) { @@ -337,47 +334,6 @@ app.post("/encryption/initialize", async (req, res) => { } }); -app.post("/encryption/migrate", async (req, res) => { - try { - const { dryRun = false } = req.body; - - const migration = new SecurityMigration({ - dryRun, - backupEnabled: true, - }); - - if (dryRun) { - apiLogger.info("Starting encryption migration (dry run)", { - operation: "encryption_migrate_dry_run", - }); - - res.json({ - success: true, - message: "Dry run mode - no changes made", - dryRun: true, - }); - } else { - apiLogger.info("Starting encryption migration", { - operation: "encryption_migrate", - }); - - await migration.runMigration(); - - res.json({ - success: true, - message: "Migration completed successfully", - }); - } - } catch (error) { - apiLogger.error("Migration failed", error, { - operation: "encryption_migrate_failed", - }); - res.status(500).json({ - error: "Migration failed", - details: error instanceof Error ? error.message : "Unknown error", - }); - } -}); app.post("/encryption/regenerate", async (req, res) => { try { @@ -654,7 +610,6 @@ app.listen(PORT, async () => { "/releases/rss", "/encryption/status", "/encryption/initialize", - "/encryption/migrate", "/encryption/regenerate", "/database/export", "/database/import", diff --git a/src/backend/database/routes/users.ts b/src/backend/database/routes/users.ts index 6a895584..dd338101 100644 --- a/src/backend/database/routes/users.ts +++ b/src/backend/database/routes/users.ts @@ -7,6 +7,7 @@ import { fileManagerPinned, fileManagerShortcuts, dismissedAlerts, + settings, } from "../db/schema.js"; import { eq, and } from "drizzle-orm"; import bcrypt from "bcryptjs"; @@ -18,7 +19,6 @@ import type { Request, Response, NextFunction } from "express"; import { authLogger, apiLogger } from "../../utils/logger.js"; import { SecuritySession } from "../../utils/security-session.js"; import { UserKeyManager } from "../../utils/user-key-manager.js"; -import { SecurityMigration } from "../../utils/security-migration.js"; // Get security session instance const securitySession = SecuritySession.getInstance(); @@ -785,24 +785,29 @@ router.post("/login", async (req, res) => { return res.status(401).json({ error: "Incorrect password" }); } - // Check and handle user migration (from old encryption system) - let migrationPerformed = false; + // Check if legacy user needs encryption setup try { - migrationPerformed = await SecurityMigration.handleUserLoginMigration(userRecord.id, password); - if (migrationPerformed) { - authLogger.success("User encryption migrated during login", { - operation: "login_migration_success", + const kekSalt = await db + .select() + .from(settings) + .where(eq(settings.key, `user_kek_salt_${userRecord.id}`)); + + if (kekSalt.length === 0) { + // Legacy user first login - set up new encryption + await securitySession.registerUser(userRecord.id, password); + authLogger.success("Legacy user encryption initialized", { + operation: "legacy_user_setup", username, userId: userRecord.id, }); } - } catch (migrationError) { - authLogger.error("Failed to migrate user during login", migrationError, { - operation: "login_migration_failed", + } catch (setupError) { + authLogger.error("Failed to initialize user encryption", setupError, { + operation: "user_encryption_setup_failed", username, userId: userRecord.id, }); - // Migration failure should not block login, but needs to be logged + // Encryption setup failure should not block login for existing users } // Unlock user data keys diff --git a/src/backend/utils/security-migration.ts b/src/backend/utils/security-migration.ts deleted file mode 100644 index 422d8937..00000000 --- a/src/backend/utils/security-migration.ts +++ /dev/null @@ -1,449 +0,0 @@ -#!/usr/bin/env node -import { db } from "../database/db/index.js"; -import { settings, users, sshData, sshCredentials } from "../database/db/schema.js"; -import { eq, sql } from "drizzle-orm"; -import { SecuritySession } from "./security-session.js"; -import { UserKeyManager } from "./user-key-manager.js"; -import { DatabaseEncryption } from "./database-encryption.js"; -import { EncryptedDBOperations } from "./encrypted-db-operations.js"; -import { FieldEncryption } from "./encryption.js"; -import { databaseLogger } from "./logger.js"; - -interface MigrationConfig { - dryRun?: boolean; - backupEnabled?: boolean; - forceRegeneration?: boolean; -} - -interface MigrationResult { - success: boolean; - usersProcessed: number; - recordsMigrated: number; - errors: string[]; - warnings: string[]; -} - -/** - * SecurityMigration - Migrate from old encryption system to KEK-DEK architecture - * - * Migration steps: - * 1. Detect existing system state - * 2. Backup existing data - * 3. Initialize new security system - * 4. Set up KEK-DEK for existing users - * 5. Migrate encrypted data - * 6. Clean up old keys - */ -class SecurityMigration { - private config: MigrationConfig; - private securitySession: SecuritySession; - private userKeyManager: UserKeyManager; - - constructor(config: MigrationConfig = {}) { - this.config = { - dryRun: config.dryRun ?? false, - backupEnabled: config.backupEnabled ?? true, - forceRegeneration: config.forceRegeneration ?? false, - }; - - this.securitySession = SecuritySession.getInstance(); - this.userKeyManager = UserKeyManager.getInstance(); - } - - /** - * Run complete migration - */ - async runMigration(): Promise { - const result: MigrationResult = { - success: false, - usersProcessed: 0, - recordsMigrated: 0, - errors: [], - warnings: [], - }; - - try { - databaseLogger.info("Starting security migration to KEK-DEK architecture", { - operation: "security_migration_start", - dryRun: this.config.dryRun, - backupEnabled: this.config.backupEnabled, - }); - - // 1. Check migration prerequisites - await this.validatePrerequisites(); - - // 2. Create backup - if (this.config.backupEnabled && !this.config.dryRun) { - await this.createBackup(); - } - - // 3. Initialize new security system - await this.initializeNewSecurity(); - - // 4. Detect users needing migration - const usersToMigrate = await this.detectUsersNeedingMigration(); - result.warnings.push(`Found ${usersToMigrate.length} users that need migration`); - - // 5. Process each user - for (const user of usersToMigrate) { - try { - await this.migrateUser(user, result); - result.usersProcessed++; - } catch (error) { - const errorMsg = `Failed to migrate user ${user.username}: ${error instanceof Error ? error.message : 'Unknown error'}`; - result.errors.push(errorMsg); - databaseLogger.error("User migration failed", error, { - operation: "user_migration_failed", - userId: user.id, - username: user.username, - }); - } - } - - // 6. Clean up old system (if all users migrated successfully) - if (result.errors.length === 0 && !this.config.dryRun) { - await this.cleanupOldSystem(); - } - - result.success = result.errors.length === 0; - - databaseLogger.success("Security migration completed", { - operation: "security_migration_complete", - result, - }); - - return result; - - } catch (error) { - const errorMsg = `Migration failed: ${error instanceof Error ? error.message : 'Unknown error'}`; - result.errors.push(errorMsg); - databaseLogger.error("Security migration failed", error, { - operation: "security_migration_failed", - }); - return result; - } - } - - /** - * Validate migration prerequisites - */ - private async validatePrerequisites(): Promise { - databaseLogger.info("Validating migration prerequisites", { - operation: "migration_validation", - }); - - // Check database connection - try { - await db.select().from(settings).limit(1); - } catch (error) { - throw new Error("Database connection failed"); - } - - // Check for old encryption keys - const oldEncryptionKey = await db - .select() - .from(settings) - .where(eq(settings.key, "db_encryption_key")); - - if (oldEncryptionKey.length === 0) { - databaseLogger.info("No old encryption key found - fresh installation", { - operation: "migration_validation", - }); - } else { - databaseLogger.info("Old encryption key detected - migration needed", { - operation: "migration_validation", - }); - } - - databaseLogger.success("Prerequisites validation passed", { - operation: "migration_validation_complete", - }); - } - - /** - * Create pre-migration backup - */ - private async createBackup(): Promise { - databaseLogger.info("Creating migration backup", { - operation: "migration_backup", - }); - - try { - const fs = await import("fs"); - const path = await import("path"); - const dataDir = process.env.DATA_DIR || "./db/data"; - const dbPath = path.join(dataDir, "db.sqlite"); - const backupPath = path.join(dataDir, `migration-backup-${Date.now()}.sqlite`); - - if (fs.existsSync(dbPath)) { - fs.copyFileSync(dbPath, backupPath); - databaseLogger.success(`Migration backup created: ${backupPath}`, { - operation: "migration_backup_complete", - backupPath, - }); - } - } catch (error) { - databaseLogger.error("Failed to create migration backup", error, { - operation: "migration_backup_failed", - }); - throw error; - } - } - - /** - * Initialize new security system - */ - private async initializeNewSecurity(): Promise { - databaseLogger.info("Initializing new security system", { - operation: "new_security_init", - }); - - await this.securitySession.initialize(); - DatabaseEncryption.initialize(); - - const isValid = await this.securitySession.validateSecuritySystem(); - if (!isValid) { - throw new Error("New security system validation failed"); - } - - databaseLogger.success("New security system initialized", { - operation: "new_security_init_complete", - }); - } - - /** - * Detect users needing migration - */ - private async detectUsersNeedingMigration(): Promise { - const allUsers = await db.select().from(users); - const usersNeedingMigration = []; - - for (const user of allUsers) { - // Check if user already has KEK salt (new system) - const kekSalt = await db - .select() - .from(settings) - .where(eq(settings.key, `user_kek_salt_${user.id}`)); - - if (kekSalt.length === 0) { - usersNeedingMigration.push(user); - } - } - - databaseLogger.info(`Found ${usersNeedingMigration.length} users needing migration`, { - operation: "migration_user_detection", - totalUsers: allUsers.length, - needingMigration: usersNeedingMigration.length, - }); - - return usersNeedingMigration; - } - - /** - * Migrate single user - */ - private async migrateUser(user: any, result: MigrationResult): Promise { - databaseLogger.info(`Migrating user: ${user.username}`, { - operation: "user_migration_start", - userId: user.id, - username: user.username, - }); - - if (this.config.dryRun) { - databaseLogger.info(`[DRY RUN] Would migrate user: ${user.username}`, { - operation: "user_migration_dry_run", - userId: user.id, - }); - return; - } - - // Issue: We need user's plaintext password to set up KEK - // but we only have password hash. Solutions: - // 1. Require user to re-enter password on first login - // 2. Generate temporary password and require user to change it - // - // For demonstration, we skip actual KEK setup and just mark user for password reset - - try { - // Mark user needing encryption reset - await db.insert(settings).values({ - key: `user_migration_required_${user.id}`, - value: JSON.stringify({ - userId: user.id, - username: user.username, - migrationTime: new Date().toISOString(), - reason: "Security system upgrade - password re-entry required", - }), - }); - - result.warnings.push(`User ${user.username} marked for password re-entry on next login`); - - databaseLogger.success(`User migration prepared: ${user.username}`, { - operation: "user_migration_prepared", - userId: user.id, - username: user.username, - }); - - } catch (error) { - databaseLogger.error(`Failed to prepare user migration: ${user.username}`, error, { - operation: "user_migration_prepare_failed", - userId: user.id, - username: user.username, - }); - throw error; - } - } - - /** - * Clean up old encryption system - */ - private async cleanupOldSystem(): Promise { - databaseLogger.info("Cleaning up old encryption system", { - operation: "old_system_cleanup", - }); - - try { - // Delete old encryption keys - await db.delete(settings).where(eq(settings.key, "db_encryption_key")); - await db.delete(settings).where(eq(settings.key, "encryption_key_created")); - - // Keep JWT key (now managed by new system) - // Delete old jwt_secret, let new system take over - await db.delete(settings).where(eq(settings.key, "jwt_secret")); - await db.delete(settings).where(eq(settings.key, "jwt_secret_created")); - - databaseLogger.success("Old encryption system cleaned up", { - operation: "old_system_cleanup_complete", - }); - - } catch (error) { - databaseLogger.error("Failed to cleanup old system", error, { - operation: "old_system_cleanup_failed", - }); - throw error; - } - } - - /** - * Check migration status - */ - static async checkMigrationStatus(): Promise<{ - migrationRequired: boolean; - usersNeedingMigration: number; - hasOldSystem: boolean; - hasNewSystem: boolean; - }> { - try { - // Check for old system - const oldEncryptionKey = await db - .select() - .from(settings) - .where(eq(settings.key, "db_encryption_key")); - - // Check for new system - const newSystemJWT = await db - .select() - .from(settings) - .where(eq(settings.key, "system_jwt_secret")); - - // Check users needing migration - const allUsers = await db.select().from(users); - let usersNeedingMigration = 0; - - for (const user of allUsers) { - const kekSalt = await db - .select() - .from(settings) - .where(eq(settings.key, `user_kek_salt_${user.id}`)); - - if (kekSalt.length === 0) { - usersNeedingMigration++; - } - } - - const hasOldSystem = oldEncryptionKey.length > 0; - const hasNewSystem = newSystemJWT.length > 0; - const migrationRequired = hasOldSystem || usersNeedingMigration > 0; - - return { - migrationRequired, - usersNeedingMigration, - hasOldSystem, - hasNewSystem, - }; - - } catch (error) { - databaseLogger.error("Failed to check migration status", error, { - operation: "migration_status_check_failed", - }); - throw error; - } - } - - /** - * Handle user login migration (when user enters password) - */ - static async handleUserLoginMigration(userId: string, password: string): Promise { - try { - // Check if user needs migration - const migrationRequired = await db - .select() - .from(settings) - .where(eq(settings.key, `user_migration_required_${userId}`)); - - if (migrationRequired.length === 0) { - return false; // No migration needed - } - - databaseLogger.info("Performing user migration during login", { - operation: "login_migration_start", - userId, - }); - - // Initialize user encryption - const securitySession = SecuritySession.getInstance(); - await securitySession.registerUser(userId, password); - - // Delete migration marker - await db.delete(settings).where(eq(settings.key, `user_migration_required_${userId}`)); - - databaseLogger.success("User migration completed during login", { - operation: "login_migration_complete", - userId, - }); - - return true; // Migration completed - - } catch (error) { - databaseLogger.error("Login migration failed", error, { - operation: "login_migration_failed", - userId, - }); - throw error; - } - } -} - -// CLI execution -if (import.meta.url === `file://${process.argv[1]}`) { - const config: MigrationConfig = { - dryRun: process.env.DRY_RUN === "true", - backupEnabled: process.env.BACKUP_ENABLED !== "false", - forceRegeneration: process.env.FORCE_REGENERATION === "true", - }; - - const migration = new SecurityMigration(config); - - migration - .runMigration() - .then((result) => { - console.log("Migration completed:", result); - process.exit(result.success ? 0 : 1); - }) - .catch((error) => { - console.error("Migration failed:", error.message); - process.exit(1); - }); -} - -export { SecurityMigration, type MigrationConfig, type MigrationResult }; \ No newline at end of file