From: Chris Duncan Date: Wed, 3 Sep 2025 13:26:54 +0000 (-0700) Subject: Move password to CryptoKey conversion to separate export. X-Git-Tag: v0.10.5~35^2~1 X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=69d24a857fdfc367255412a5221f44e945a15aa4;p=libnemo.git Move password to CryptoKey conversion to separate export. --- diff --git a/src/lib/vault/passkey.ts b/src/lib/vault/passkey.ts new file mode 100644 index 0000000..fdcfe07 --- /dev/null +++ b/src/lib/vault/passkey.ts @@ -0,0 +1,47 @@ +//! SPDX-FileCopyrightText: 2025 Chris Duncan +//! SPDX-License-Identifier: GPL-3.0-or-later + +export async function createKeyFromPassword (action: string, salt: ArrayBuffer, data: { [key: string]: unknown }): Promise { + // Allowlisted wallet actions + if (['create', 'load', 'unlock', 'update'].includes(action)) { + + // Create local copy of password ASAP, then clear bytes from original buffer + if (!(data.password instanceof ArrayBuffer)) { + throw new TypeError('Password must be ArrayBuffer') + } + + const password = data.password.slice() + new Uint8Array(data.password).fill(0) + delete data.password + + // Only unlocking should decrypt the vault; other sensitive actions should + // throw if the vault is still locked and encrypted + const purpose = action === 'unlock' ? 'decrypt' : 'encrypt' + + return crypto.subtle + .importKey('raw', password, 'PBKDF2', false, ['deriveKey']) + .then(derivationKey => { + new Uint8Array(password).fill(0).buffer.transfer?.() + const derivationAlgorithm: Pbkdf2Params = { + name: 'PBKDF2', + hash: 'SHA-512', + iterations: 210000, + salt + } + const derivedKeyType: AesKeyGenParams = { + name: 'AES-GCM', + length: 256 + } + return crypto.subtle + .deriveKey(derivationAlgorithm, derivationKey, derivedKeyType, false, [purpose]) + }) + .catch(err => { + console.error(err) + throw new Error('Failed to derive CryptoKey from password', { cause: err }) + }) + } else if (data.password !== undefined) { + throw new Error('Password is not allowed for this action', { cause: action }) + } else { + return Promise.resolve(undefined) + } +} diff --git a/src/lib/vault/vault-worker.ts b/src/lib/vault/vault-worker.ts index cfe5a26..c50ad33 100644 --- a/src/lib/vault/vault-worker.ts +++ b/src/lib/vault/vault-worker.ts @@ -6,6 +6,7 @@ import { NamedData } from '#types' import { BIP44_COIN_NANO } from '../constants' import { utf8 } from '../convert' import { Bip39, Bip44, Blake2b, NanoNaCl } from '../crypto' +import { createKeyFromPassword } from './passkey' import { VaultTimer } from './vault-timer' /** @@ -26,15 +27,16 @@ export class VaultWorker { this.#seed = undefined this.#mnemonic = undefined NODE: this.#parentPort = parentPort + const listener = (event: MessageEvent): void => { const data = this.#parseData(event.data) const action = this.#parseAction(data) - const type = this.#parseType(action, data) const keySalt = this.#parseKeySalt(action, data) - const iv = this.#parseIv(action, data) - const { seed, mnemonicPhrase, mnemonicSalt, index, encrypted, message } = this.#extractData(action, data) - this.#createPasskey(action, keySalt, data) + createKeyFromPassword(action, keySalt, data) .then((key: CryptoKey | undefined): Promise => { + const type = this.#parseType(action, data) + const iv = this.#parseIv(action, data) + const { seed, mnemonicPhrase, mnemonicSalt, index, encrypted, message } = this.#extractData(action, data) switch (action) { case 'STOP': { BROWSER: close() @@ -324,51 +326,6 @@ export class VaultWorker { } } - #createPasskey (action: string, salt: ArrayBuffer, data: { [key: string]: unknown }) { - // Allowlisted wallet actions - if (['create', 'load', 'unlock', 'update'].includes(action)) { - - // Create local copy of password ASAP, then clear bytes from original buffer - if (!(data.password instanceof ArrayBuffer)) { - throw new TypeError('Password must be ArrayBuffer') - } - - const password = data.password.slice() - new Uint8Array(data.password).fill(0) - delete data.password - - // Only unlocking should decrypt the vault; other sensitive actions should - // throw if the vault is still locked and encrypted - const purpose = action === 'unlock' ? 'decrypt' : 'encrypt' - - return crypto.subtle - .importKey('raw', password, 'PBKDF2', false, ['deriveKey']) - .then(derivationKey => { - new Uint8Array(password).fill(0).buffer.transfer?.() - const derivationAlgorithm: Pbkdf2Params = { - name: 'PBKDF2', - hash: 'SHA-512', - iterations: 210000, - salt - } - const derivedKeyType: AesKeyGenParams = { - name: 'AES-GCM', - length: 256 - } - return crypto.subtle - .deriveKey(derivationAlgorithm, derivationKey, derivedKeyType, false, [purpose]) - }) - .catch(err => { - console.error(err) - throw new Error('Failed to derive CryptoKey from password', { cause: err }) - }) - } else if (data.password !== undefined) { - throw new Error('Password is not allowed for this action', { cause: action }) - } else { - return Promise.resolve(undefined) - } - } - #decryptWallet (type: string, key: CryptoKey, iv: ArrayBuffer, encrypted: ArrayBuffer): Promise { const seedLength = type === 'BIP-44' ? 64 : 32 const additionalData = utf8.toBytes(type)