From: Chris Duncan Date: Fri, 25 Jul 2025 12:31:28 +0000 (-0700) Subject: Create dedicated export method for Safe. X-Git-Tag: v0.10.5~50^2~21^2~4 X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=5d8d4a936852ae1f778ebf550d05931428ab61ab;p=libnemo.git Create dedicated export method for Safe. --- diff --git a/src/lib/workers/safe.ts b/src/lib/workers/safe.ts index 7b8690c..591ad8c 100644 --- a/src/lib/workers/safe.ts +++ b/src/lib/workers/safe.ts @@ -48,7 +48,7 @@ export class Safe extends WorkerInterface { return await this.fetch(name, store, password) } case 'export': { - return await this.fetch(name, store, password, true) + return await this.export(store, password) } case 'destroy': { return { result: await this.destroy(data, store) } @@ -113,7 +113,7 @@ export class Safe extends WorkerInterface { /** * Retrieves data from the Safe and decrypts it with a password byte array. */ - static async fetch (name: string | string[] | unknown, store: string | unknown, password: ArrayBuffer | unknown, all: boolean = false): Promise> { + static async fetch (name: string | string[] | unknown, store: string | unknown, password: ArrayBuffer | unknown): Promise> { const names = Array.isArray(name) ? name : [name] if (names.some(v => typeof v !== 'string')) { throw new Error('Invalid fields') @@ -128,9 +128,7 @@ export class Safe extends WorkerInterface { const results: NamedData = {} try { - const records: SafeRecord[] = all - ? await this.#getAll(store) - : await this.#get(fields, store) + const records: SafeRecord[] = await this.#get(fields, store) if (records == null) { throw new Error('') } @@ -151,6 +149,47 @@ export class Safe extends WorkerInterface { } } + /** + * Retrieves all data from a specified Safe table. If a password is not + * provided, the records are returned as encrypted data. + */ + static async export (store: string | unknown, password?: ArrayBuffer | unknown): Promise> { + if (typeof store !== 'string' || store === '') { + throw new Error('Invalid database store name') + } + if (password != null && !(password instanceof ArrayBuffer)) { + throw new Error('Invalid password') + } + + const results: NamedData = {} + try { + const records: SafeRecord[] = await this.#getAll(store) + if (records == null) { + throw new Error('') + } + if (password instanceof ArrayBuffer) { + const decryptionKeys: { [salt: string]: CryptoKey } = {} + for (const record of records) { + const salt = await Entropy.import(record.salt) + decryptionKeys[salt.hex] ??= await this.#createAesKey('decrypt', password, salt.buffer) + const iv = await Entropy.import(record.iv) + const decrypted = await globalThis.crypto.subtle.decrypt({ name: 'AES-GCM', iv: iv.buffer }, decryptionKeys[salt.hex], record.encrypted) + results[record.label] = decrypted + } + } else { + for (const record of records) { + results[record.label] = record.encrypted + } + } + return results + } catch (err) { + console.error(err) + throw new Error(`Failed to export ${store} records`, { cause: err }) + } finally { + if (password instanceof ArrayBuffer) bytes.erase(password) + } + } + static async #createAesKey (purpose: 'encrypt' | 'decrypt', password: ArrayBuffer, salt: ArrayBuffer): Promise { const derivationKey = await globalThis.crypto.subtle.importKey('raw', password, 'PBKDF2', false, ['deriveBits', 'deriveKey']) const derivationAlgorithm: Pbkdf2Params = {