fix: harden managed online backup key handling

Require canonical encrypted envelopes and surface typed sanitized keyring corruption states.

Persist crash-durable primary and recovery files, prevalidate removal plans, isolate renderer refresh authority, and cover the real hidden Windows DPAPI and IPC composition.
This commit is contained in:
Sucukdeluxe
2026-08-22 13:28:47 +02:00
parent 39e0dd104e
commit 49fa4518c9
12 changed files with 1626 additions and 505 deletions
+48 -15
View File
@@ -3,6 +3,7 @@ const {
deleteOnlineBackup,
uploadOnlineBackup
} = require('./online-backup');
const { KEYRING_ERROR_CODES } = require('./online-backup-keyring');
const ERRORS = Object.freeze({
list: 'Online-Sicherungen konnten nicht geladen werden',
@@ -11,6 +12,16 @@ const ERRORS = Object.freeze({
delete: 'Online-Sicherung konnte nicht gelöscht werden',
notFound: 'Online-Sicherungsschlüssel wurde nicht gefunden'
});
const KEYRING_MESSAGES = Object.freeze({
[KEYRING_ERROR_CODES.structure]: 'Gespeicherter Online-Schlüsselbund ist beschädigt',
[KEYRING_ERROR_CODES.unavailable]: 'Sichere Schlüsselspeicherung ist nicht verfügbar',
[KEYRING_ERROR_CODES.decrypt]: 'Gespeicherter Online-Sicherungsschlüssel konnte nicht entschlüsselt werden',
[KEYRING_ERROR_CODES.mismatch]: 'Gespeicherte Online-Sicherungskennung stimmt nicht mit dem Schlüssel überein',
[KEYRING_ERROR_CODES.duplicate]: 'Gespeicherte Online-Sicherungskennung ist mehrdeutig',
[KEYRING_ERROR_CODES.recovered]: 'Online-Schlüsselbund wurde aus einer Wiederherstellungsdatei geladen',
[KEYRING_ERROR_CODES.encrypt]: 'Online-Sicherungsschlüssel konnte nicht sicher vorbereitet werden',
[KEYRING_ERROR_CODES.plan]: 'Online-Sicherung konnte lokal nicht eindeutig entfernt werden'
});
function sanitizeEntry(entry) {
return {
@@ -28,6 +39,12 @@ function sanitizeCreatedEntry(entry, key) {
};
}
function keyringFailure(error, fallback) {
const message = KEYRING_MESSAGES[error?.code];
if (!message) return { ok: false, error: fallback };
return { ok: false, code: error.code, error: message };
}
function createOnlineBackupManager({
keyring,
loadSettings,
@@ -46,8 +63,12 @@ function createOnlineBackupManager({
}
async function listEntries() {
const entries = await keyring.list();
return entries.map(sanitizeEntry);
const snapshot = await keyring.list();
if (!snapshot || !Array.isArray(snapshot.entries) || !Array.isArray(snapshot.issues)) throw new Error(ERRORS.list);
return {
entries: snapshot.entries.map(sanitizeEntry),
issues: snapshot.issues
};
}
async function createTransaction() {
@@ -68,27 +89,39 @@ function createOnlineBackupManager({
}
async function deleteTransaction(id) {
const key = await keyring.getKey(id);
if (!key) return { ok: false, notFound: true, error: ERRORS.notFound };
const outcome = await deleteBackup(key);
const plan = await keyring.prepareRemove(id);
if (!plan) return { ok: false, notFound: true, error: ERRORS.notFound };
const outcome = await deleteBackup(plan.key);
if (!outcome?.deleted && !outcome?.notFound) throw new Error(ERRORS.delete);
await keyring.remove(id);
await keyring.commitRemove(plan);
return { ok: true, removedId: id, notFound: outcome.notFound };
}
async function listManaged() {
try {
return { ok: true, entries: await listEntries() };
} catch {
return { ok: false, error: ERRORS.list };
const snapshot = await listEntries();
const issue = snapshot.issues[0];
if (!issue) return { ok: true, entries: snapshot.entries };
const message = KEYRING_MESSAGES[issue] || ERRORS.list;
if (snapshot.entries.length || issue === KEYRING_ERROR_CODES.recovered) {
return {
ok: true,
entries: snapshot.entries,
warningCode: issue,
warning: message
};
}
return { ok: false, entries: [], code: issue, error: message };
} catch (error) {
return keyringFailure(error, ERRORS.list);
}
}
async function createManaged() {
try {
return await serialize(createTransaction);
} catch {
return { ok: false, error: ERRORS.create };
} catch (error) {
return keyringFailure(error, ERRORS.create);
}
}
@@ -98,16 +131,16 @@ function createOnlineBackupManager({
if (!key) return { ok: false, notFound: true, error: ERRORS.notFound };
await copyText(key);
return { ok: true };
} catch {
return { ok: false, error: ERRORS.copy };
} catch (error) {
return keyringFailure(error, ERRORS.copy);
}
}
async function deleteManaged(id) {
try {
return await serialize(() => deleteTransaction(id));
} catch {
return { ok: false, error: ERRORS.delete };
} catch (error) {
return keyringFailure(error, ERRORS.delete);
}
}