From fd912b5534d70dd8fd0e48a46a04829c127a2efb Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Sat, 26 Jul 2025 23:48:06 -0700 Subject: [PATCH] Remove passkey worker since salts are locked up in Safe anyway. --- src/lib/account.ts | 2 +- src/lib/rolodex.ts | 9 +++-- src/lib/workers/passkey.ts | 80 -------------------------------------- src/lib/workers/safe.ts | 50 ++++++++++++------------ src/types.d.ts | 2 +- 5 files changed, 31 insertions(+), 112 deletions(-) delete mode 100644 src/lib/workers/passkey.ts diff --git a/src/lib/account.ts b/src/lib/account.ts index 56a2b8a..d4d259f 100644 --- a/src/lib/account.ts +++ b/src/lib/account.ts @@ -79,7 +79,7 @@ export class Account { await SafeWorker.request({ method: 'destroy', store: 'Account', - [this.publicKey]: this.publicKey + name: this.publicKey }) } catch (err) { console.error(err) diff --git a/src/lib/rolodex.ts b/src/lib/rolodex.ts index e3efbd5..527be64 100644 --- a/src/lib/rolodex.ts +++ b/src/lib/rolodex.ts @@ -95,7 +95,7 @@ export class Rolodex { const { result } = await SafeWorker.request({ method: 'destroy', store: 'Rolodex', - [address]: address + name: address }) return result } @@ -109,13 +109,14 @@ export class Rolodex { static async deleteName (name: string): Promise { const data: NamedData = { method: 'destroy', - store: 'Rolodex', - [name]: name + store: 'Rolodex' } + const names: string[] = [name] const addresses = await this.getAddresses(name) for (const address of addresses) { - data[address] = address + names.push(address) } + data.name = JSON.stringify(names) const { result } = await SafeWorker.request(data) return result } diff --git a/src/lib/workers/passkey.ts b/src/lib/workers/passkey.ts deleted file mode 100644 index 2082fbe..0000000 --- a/src/lib/workers/passkey.ts +++ /dev/null @@ -1,80 +0,0 @@ -//! SPDX-FileCopyrightText: 2025 Chris Duncan -//! SPDX-License-Identifier: GPL-3.0-or-later - -'use strict' - -import { WorkerInterface } from './worker-interface' -import { NamedData } from '#types' - -/** -* Converts a user password to a `CryptoKey`. -*/ -export class Passkey extends WorkerInterface { - static { - this.listen() - } - - static async work (data: NamedData | unknown): Promise> { - const { purpose, password, salt } = this.#extractData(data) - try { - const key = await this.#createAesKey(purpose, password, salt) - return { salt, key } - } catch (err) { - throw new Error('Failed to derive key from password', { cause: err }) - } finally { - password.transfer() - } - } - - static #extractData (data: unknown) { - if (data == null) { - throw new TypeError('Worker received no data') - } - if (typeof data !== 'object') { - throw new Error('Invalid data') - } - const dataObject = data as { [key: string]: unknown } - if (!('password' in dataObject) || !(dataObject.password instanceof ArrayBuffer)) { - throw new TypeError('Password must be ArrayBuffer') - } - const password: ArrayBuffer = dataObject.password - if (!('purpose' in dataObject)) { - throw new TypeError('Key purpose is required') - } - if (dataObject.purpose !== 'encrypt' && dataObject.purpose !== 'decrypt') { - throw new TypeError('Invalid key purpose') - } - const purpose: 'encrypt' | 'decrypt' = dataObject.purpose - if (purpose === 'decrypt' && !('salt' in dataObject)) { - throw new TypeError('Salt required for decryption key') - } - if (dataObject.salt != null && !(dataObject.salt instanceof ArrayBuffer)) { - throw new TypeError('Salt must be ArrayBuffer') - } - const salt: ArrayBuffer = dataObject.salt ?? globalThis.crypto.getRandomValues(new Uint8Array(32)).buffer - return { purpose, password, salt } - } - - 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 = { - name: 'PBKDF2', - hash: 'SHA-512', - iterations: 210000, - salt - } - const derivedKeyType: AesKeyGenParams = { - name: 'AES-GCM', - length: 256 - } - return await globalThis.crypto.subtle.deriveKey(derivationAlgorithm, derivationKey, derivedKeyType, false, [purpose]) - } -} - -let importWorkerThreads = '' -NODE: importWorkerThreads = `import { parentPort } from 'node:worker_threads'` -export default ` - ${importWorkerThreads} - const WorkerInterface = ${WorkerInterface} - const Passkey = ${Passkey} -` diff --git a/src/lib/workers/safe.ts b/src/lib/workers/safe.ts index 591ad8c..86b53bc 100644 --- a/src/lib/workers/safe.ts +++ b/src/lib/workers/safe.ts @@ -22,22 +22,29 @@ export class Safe extends WorkerInterface { this.listen() } - static async work (data: NamedData): Promise> { - const { method, name, store } = data + static async work (data: NamedData | unknown): Promise> { + if (data == null) { + throw new TypeError('Worker received no data') + } + if (typeof data !== 'object') { + throw new TypeError('Invalid data') + } + const { method, names, store, password } = data as { [key: string]: unknown } if (typeof method !== 'string') { throw new TypeError('Invalid method') } - delete data.method - if (name != null && typeof name !== 'string') { - throw new TypeError('Invalid name') + function validateNames (names: unknown): asserts names is string[] { + if (!Array.isArray(names) || names.some(n => typeof n !== 'string')) { + throw new TypeError('Invalid name') + } } - delete data.name + validateNames(names) if (typeof store !== 'string') { throw new TypeError('Invalid store') } - delete data.store - const password = data.password - delete data.password + if (password != null && !(password instanceof ArrayBuffer)) { + throw new TypeError('Invalid password') + } this.#storage = await this.#open(this.DB_NAME) try { switch (method) { @@ -45,13 +52,13 @@ export class Safe extends WorkerInterface { return { result: await this.store(data, store, password) } } case 'fetch': { - return await this.fetch(name, store, password) + return await this.fetch(names, store, password) } case 'export': { return await this.export(store, password) } case 'destroy': { - return { result: await this.destroy(data, store) } + return { result: await this.destroy(names, store) } } default: { throw new Error(`unknown Safe method ${method}`) @@ -66,9 +73,9 @@ export class Safe extends WorkerInterface { /** * Removes data from the Safe without decrypting. */ - static async destroy (data: NamedData, store: string): Promise { + static async destroy (names: string[], store: string): Promise { try { - return await this.#delete(Object.keys(data), store) + return await this.#delete(names, store) } catch (err) { console.error(err) throw new Error(this.ERR_MSG) @@ -113,22 +120,13 @@ 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): Promise> { - const names = Array.isArray(name) ? name : [name] - if (names.some(v => typeof v !== 'string')) { - throw new Error('Invalid fields') + static async fetch (names: string[], store: string, password: ArrayBuffer | unknown): Promise> { + if (password == null || !(password instanceof ArrayBuffer)) { + throw new TypeError('Invalid password') } - const fields: string[] = names - if (typeof store !== 'string' || store === '') { - throw new Error('Invalid database store name') - } - if (!(password instanceof ArrayBuffer)) { - throw new Error('Invalid password') - } - const results: NamedData = {} try { - const records: SafeRecord[] = await this.#get(fields, store) + const records: SafeRecord[] = await this.#get(names, store) if (records == null) { throw new Error('') } diff --git a/src/types.d.ts b/src/types.d.ts index 3bf50bf..a68fc92 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -377,7 +377,7 @@ export declare class ChangeBlock extends Block { constructor (account: Account | string, balance: string, representative: Account | string, frontier: string, work?: string) } -export type Data = boolean | number[] | string | ArrayBuffer | CryptoKey +export type Data = boolean | number[] | string | string[] | ArrayBuffer | CryptoKey /** * Represents a cryptographically strong source of entropy suitable for use in -- 2.47.3