Files
harheimertc/scripts/re-encrypt-data.js

353 lines
13 KiB
JavaScript
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* Script zum Neuverschlüsseln aller verschlüsselten Daten
*
* Verwendung:
* node scripts/re-encrypt-data.js [--old-key="alter-schlüssel"]
*
* Wenn --old-key nicht angegeben wird, wird der Standard-Entwicklungsschlüssel verwendet.
* Der neue Schlüssel wird aus der .env Datei gelesen (ENCRYPTION_KEY).
*/
import fs from 'fs/promises'
import path from 'path'
import { fileURLToPath } from 'url'
import { decryptObject, encryptObject, decrypt, encrypt } from '../server/utils/encryption.js'
import dotenv from 'dotenv'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
// Lade .env Datei
dotenv.config({ path: path.join(__dirname, '..', '.env') })
// Alte Standard-Schlüssel (die möglicherweise verwendet wurden)
const OLD_DEFAULT_KEYS = [
'default-key-change-in-production',
'local_development_encryption_key_change_in_production'
]
// Neuer Schlüssel aus .env
const NEW_KEY = process.env.ENCRYPTION_KEY
if (!NEW_KEY) {
console.error('❌ FEHLER: ENCRYPTION_KEY ist nicht in der .env Datei gesetzt!')
console.error('Bitte setzen Sie ENCRYPTION_KEY in der .env Datei.')
process.exit(1)
}
// Alten Schlüssel aus Kommandozeilenargumenten oder Standard verwenden
const args = process.argv.slice(2)
let oldKeyArg = null
for (const arg of args) {
if (arg.startsWith('--old-key=')) {
oldKeyArg = arg.split('=')[1]
}
}
// Pfade bestimmen
function getDataPath(filename) {
const cwd = process.cwd()
if (cwd.endsWith('.output')) {
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal
return path.join(cwd, '../server/data', filename)
}
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal
return path.join(cwd, 'server/data', filename)
}
const USERS_FILE = getDataPath('users.json')
const MEMBERS_FILE = getDataPath('members.json')
const MEMBERSHIP_APPLICATIONS_DIR = getDataPath('membership-applications')
// Backup-Verzeichnis erstellen
async function createBackup() {
const backupDir = path.join(__dirname, '..', 'backups', `re-encrypt-${Date.now()}`) // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal
await fs.mkdir(backupDir, { recursive: true })
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
console.log(`📦 Backup-Verzeichnis erstellt: ${backupDir}`)
return backupDir
}
// Prüft ob Daten verschlüsselt sind
function isEncrypted(data) {
try {
const parsed = JSON.parse(data.trim())
if (Array.isArray(parsed)) {
return false
}
if (typeof parsed === 'object' && parsed !== null && !parsed.encryptedData) {
return false
}
return false
} catch (e) {
return true
}
}
// Prüft ob Daten bereits mit dem neuen Schlüssel verschlüsselt sind
async function isEncryptedWithNewKey(encryptedData) {
try {
await decryptObject(encryptedData, NEW_KEY)
return true
} catch {
return false
}
}
// Versucht mit verschiedenen Schlüsseln zu entschlüsseln
async function decryptWithFallback(encryptedData, keys) {
const errors = []
// Prüfe zuerst, ob die Daten bereits mit dem neuen Schlüssel verschlüsselt sind
if (await isEncryptedWithNewKey(encryptedData)) {
throw new Error('ALREADY_ENCRYPTED_WITH_NEW_KEY')
}
// encryptedData sollte bereits ein String sein (entweder direkt verschlüsselt oder aus einem JSON-Objekt extrahiert)
// Versuche mit jedem Schlüssel zu entschlüsseln
for (const key of keys) {
try {
// Direkt verschlüsselte Base64-Daten
return decryptObject(encryptedData, key)
} catch (error) {
errors.push({ key: key.length > 30 ? key.substring(0, 30) + '...' : key, error: error.message })
continue
}
}
throw new Error(`Entschlüsselung fehlgeschlagen mit allen Schlüsseln:\n${errors.map(e => ` - ${e.key}: ${e.error}`).join('\n')}`)
}
// Neuverschlüsselt users.json
async function reencryptUsers(backupDir, oldKeys) {
try {
const data = await fs.readFile(USERS_FILE, 'utf-8')
// Backup erstellen
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal
await fs.copyFile(USERS_FILE, path.join(backupDir, 'users.json')) // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal
console.log('✅ Backup von users.json erstellt')
if (!isEncrypted(data)) {
console.log(' users.json ist nicht verschlüsselt, überspringe...')
return
}
// Prüfe ob bereits mit neuem Schlüssel verschlüsselt
if (await isEncryptedWithNewKey(data)) {
console.log(' users.json ist bereits mit dem neuen Schlüssel verschlüsselt, überspringe...')
return
}
console.log('🔄 Entschlüssele users.json...')
const decrypted = await decryptWithFallback(data, oldKeys)
console.log('🔐 Verschlüssele users.json mit neuem Schlüssel...')
const reencrypted = encryptObject(decrypted, NEW_KEY)
await fs.writeFile(USERS_FILE, reencrypted, 'utf-8')
console.log('✅ users.json erfolgreich neu verschlüsselt')
} catch (error) {
if (error.code === 'ENOENT') {
console.log(' users.json existiert nicht, überspringe...')
return
}
if (error.message === 'ALREADY_ENCRYPTED_WITH_NEW_KEY') {
console.log(' users.json ist bereits mit dem neuen Schlüssel verschlüsselt, überspringe...')
return
}
throw error
}
}
// Neuverschlüsselt members.json
async function reencryptMembers(backupDir, oldKeys) {
try {
const data = await fs.readFile(MEMBERS_FILE, 'utf-8')
// Backup erstellen
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal
await fs.copyFile(MEMBERS_FILE, path.join(backupDir, 'members.json')) // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal
console.log('✅ Backup von members.json erstellt')
if (!isEncrypted(data)) {
console.log(' members.json ist nicht verschlüsselt, überspringe...')
return
}
// Prüfe ob bereits mit neuem Schlüssel verschlüsselt
if (await isEncryptedWithNewKey(data)) {
console.log(' members.json ist bereits mit dem neuen Schlüssel verschlüsselt, überspringe...')
return
}
console.log('🔄 Entschlüssele members.json...')
const decrypted = await decryptWithFallback(data, oldKeys)
console.log('🔐 Verschlüssele members.json mit neuem Schlüssel...')
const reencrypted = encryptObject(decrypted, NEW_KEY)
await fs.writeFile(MEMBERS_FILE, reencrypted, 'utf-8')
console.log('✅ members.json erfolgreich neu verschlüsselt')
} catch (error) {
if (error.code === 'ENOENT') {
console.log(' members.json existiert nicht, überspringe...')
return
}
if (error.message === 'ALREADY_ENCRYPTED_WITH_NEW_KEY') {
console.log(' members.json ist bereits mit dem neuen Schlüssel verschlüsselt, überspringe...')
return
}
throw error
}
}
// Neuverschlüsselt Mitgliedschaftsanträge
async function reencryptMembershipApplications(backupDir, oldKeys) {
try {
await fs.access(MEMBERSHIP_APPLICATIONS_DIR)
} catch {
console.log(' membership-applications Verzeichnis existiert nicht, überspringe...')
return
}
const files = await fs.readdir(MEMBERSHIP_APPLICATIONS_DIR)
let processed = 0
let skipped = 0
for (const file of files) {
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal
const filePath = path.join(MEMBERSHIP_APPLICATIONS_DIR, file)
const stat = await fs.stat(filePath)
if (stat.isDirectory()) {
continue
}
try {
// Backup erstellen
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal
const backupPath = path.join(backupDir, 'membership-applications', file) // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal
await fs.mkdir(path.dirname(backupPath), { recursive: true })
await fs.copyFile(filePath, backupPath)
const content = await fs.readFile(filePath, 'utf-8')
const parsed = JSON.parse(content)
// Prüfe ob encryptedData Feld vorhanden ist
if (parsed.encryptedData) {
// Prüfe ob bereits mit neuem Schlüssel verschlüsselt
if (await isEncryptedWithNewKey(parsed.encryptedData)) {
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
console.log(` ${file} ist bereits mit dem neuen Schlüssel verschlüsselt, überspringe...`)
skipped++
} else {
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
console.log(`🔄 Entschlüssele ${file}...`)
// Nur das encryptedData Feld entschlüsseln
const decrypted = await decryptWithFallback(parsed.encryptedData, oldKeys)
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
console.log(`🔐 Verschlüssele ${file} mit neuem Schlüssel...`)
const reencrypted = encryptObject(decrypted, NEW_KEY)
parsed.encryptedData = reencrypted
await fs.writeFile(filePath, JSON.stringify(parsed, null, 2), 'utf-8')
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
console.log(`${file} erfolgreich neu verschlüsselt`)
processed++
}
} else if (file.endsWith('.data')) {
// .data Dateien sind direkt verschlüsselt
// Prüfe ob bereits mit neuem Schlüssel verschlüsselt
if (await isEncryptedWithNewKey(content)) {
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
console.log(` ${file} ist bereits mit dem neuen Schlüssel verschlüsselt, überspringe...`)
skipped++
} else {
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
console.log(`🔄 Entschlüssele ${file}...`)
const decrypted = await decryptWithFallback(content, oldKeys)
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
console.log(`🔐 Verschlüssele ${file} mit neuem Schlüssel...`)
const reencrypted = encrypt(JSON.stringify(decrypted), NEW_KEY)
await fs.writeFile(filePath, reencrypted, 'utf-8')
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
console.log(`${file} erfolgreich neu verschlüsselt`)
processed++
}
} else {
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
console.log(` ${file} enthält keine verschlüsselten Daten, überspringe...`)
skipped++
}
} catch (error) {
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
// file is from readdir, not user input; error.message is safe
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
console.error(`❌ Fehler beim Verarbeiten von ${file}:`, error.message)
throw error
}
}
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
console.log(`✅ Mitgliedschaftsanträge verarbeitet: ${processed} neu verschlüsselt, ${skipped} übersprungen`)
}
// Hauptfunktion
async function main() {
console.log('🔐 Daten-Neuverschlüsselung gestartet\n')
// Alte Schlüssel zusammenstellen
const oldKeys = oldKeyArg ? [oldKeyArg, ...OLD_DEFAULT_KEYS] : OLD_DEFAULT_KEYS
console.log('Alte Schlüssel (werden nacheinander versucht):')
oldKeys.forEach((key, i) => {
const displayKey = key.length > 50 ? key.substring(0, 50) + '...' : key
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
console.log(` ${i + 1}. ${displayKey}`)
})
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
console.log(`\nNeuer Schlüssel: ${NEW_KEY.length > 50 ? NEW_KEY.substring(0, 50) + '...' : NEW_KEY}\n`)
// Bestätigung
console.log('⚠️ WICHTIG: Dieses Script wird alle verschlüsselten Daten neu verschlüsseln!')
console.log('📦 Backups werden automatisch erstellt.\n')
// Backup-Verzeichnis erstellen
const backupDir = await createBackup()
try {
// Dateien neu verschlüsseln
await reencryptUsers(backupDir, oldKeys)
console.log('')
await reencryptMembers(backupDir, oldKeys)
console.log('')
await reencryptMembershipApplications(backupDir, oldKeys)
console.log('')
console.log('✅ Alle Daten erfolgreich neu verschlüsselt!')
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
console.log(`📦 Backups gespeichert in: ${backupDir}`)
} catch (error) {
console.error('\n❌ FEHLER beim Neuverschlüsseln:', error.message)
console.error('\nDie Backups sind sicher gespeichert. Sie können die Dateien manuell wiederherstellen.')
process.exit(1)
}
}
// Script ausführen
main().catch(error => {
console.error('Unerwarteter Fehler:', error)
process.exit(1)
})