]> git.codecow.com Git - libnemo.git/commitdiff
Move password to CryptoKey conversion to separate export.
authorChris Duncan <chris@zoso.dev>
Wed, 3 Sep 2025 13:26:54 +0000 (06:26 -0700)
committerChris Duncan <chris@zoso.dev>
Wed, 3 Sep 2025 13:26:54 +0000 (06:26 -0700)
src/lib/vault/passkey.ts [new file with mode: 0644]
src/lib/vault/vault-worker.ts

diff --git a/src/lib/vault/passkey.ts b/src/lib/vault/passkey.ts
new file mode 100644 (file)
index 0000000..fdcfe07
--- /dev/null
@@ -0,0 +1,47 @@
+//! SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
+//! SPDX-License-Identifier: GPL-3.0-or-later
+
+export async function createKeyFromPassword (action: string, salt: ArrayBuffer, data: { [key: string]: unknown }): Promise<CryptoKey | undefined> {
+       // 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)
+       }
+}
index cfe5a264d40f3a299570523f0392cd00827cef76..c50ad33d96ed2496581cafe475c00b98061c6c43 100644 (file)
@@ -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<any>): 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<NamedData> => {
+                                       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<void> {
                const seedLength = type === 'BIP-44' ? 64 : 32
                const additionalData = utf8.toBytes(type)