From 6d89eb9e76e42ea5ccc24c28ccde8770512fc0ef Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Fri, 8 Aug 2025 13:53:20 -0700 Subject: [PATCH] Require wallet type as additional metadata to decrypt. --- src/lib/safe.ts | 35 ++++++++++++++++++++++++++--------- src/lib/wallet.ts | 1 + 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/lib/safe.ts b/src/lib/safe.ts index 8a97124..6aecb1e 100644 --- a/src/lib/safe.ts +++ b/src/lib/safe.ts @@ -69,7 +69,7 @@ export class Safe { break } case 'unlock': { - result = await this.unlock(key, iv, encrypted) + result = await this.unlock(type, key, iv, encrypted) break } case 'update': { @@ -209,8 +209,11 @@ export class Safe { /** * Decrypts the input and sets the seed and, if it is included, the mnemonic. */ - static async unlock (key?: CryptoKey, iv?: ArrayBuffer, encrypted?: ArrayBuffer): Promise> { + static async unlock (type?: string, key?: CryptoKey, iv?: ArrayBuffer, encrypted?: ArrayBuffer): Promise> { try { + if (type == null) { + throw new TypeError('Wallet type is required') + } if (key == null) { throw new TypeError('Wallet password is required') } @@ -220,7 +223,7 @@ export class Safe { if (encrypted == null) { throw new TypeError('Wallet encrypted data is required') } - const { seed, mnemonic } = await this.#decryptWallet(key, iv, encrypted) + const { seed, mnemonic } = await this.#decryptWallet(type, key, iv, encrypted) if (!(seed instanceof ArrayBuffer)) { throw new TypeError('Invalid seed') } @@ -312,11 +315,21 @@ export class Safe { return await crypto.subtle.deriveKey(derivationAlgorithm, derivationKey, derivedKeyType, false, [purpose]) } - static async #decryptWallet (key: CryptoKey, iv: ArrayBuffer, encrypted: ArrayBuffer): Promise> { - const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, encrypted) - const decoded = JSON.parse(bytes.toUtf8(new Uint8Array(decrypted))) - const seed = hex.toBuffer(decoded.seed) - const mnemonic = decoded.mnemonic + static async #decryptWallet (type: string, key: CryptoKey, iv: ArrayBuffer, encrypted: ArrayBuffer): Promise> { + let decrypted, decoded, parsed + + const additionalData = utf8.toBytes(type) + decrypted = new Uint8Array(await crypto.subtle.decrypt({ name: 'AES-GCM', iv, additionalData }, key, encrypted)) + decoded = bytes.toUtf8(decrypted) + bytes.erase(decrypted) + decrypted = undefined + + parsed = JSON.parse(decoded) + decoded = undefined + + const seed = hex.toBuffer(parsed.seed) + const mnemonic = parsed.mnemonic + parsed = parsed.seed = parsed.mnemonic = undefined return { seed, mnemonic } } @@ -342,6 +355,9 @@ export class Safe { } static async #encryptWallet (key: CryptoKey): Promise> { + if (this.#type == null) { + throw new Error('Invalid wallet type') + } if (this.#seed == null) { throw new Error('Wallet seed not found') } @@ -352,8 +368,9 @@ export class Safe { // restrict iv to 96 bits per GCM best practice const iv = crypto.getRandomValues(new Uint8Array(12)).buffer + const additionalData = utf8.toBytes(this.#type) const encoded = utf8.toBytes(JSON.stringify(data)) - const encrypted = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, encoded) + const encrypted = await crypto.subtle.encrypt({ name: 'AES-GCM', iv, additionalData }, key, encoded) return { iv, encrypted } } diff --git a/src/lib/wallet.ts b/src/lib/wallet.ts index 354fdc4..bb5c12a 100644 --- a/src/lib/wallet.ts +++ b/src/lib/wallet.ts @@ -404,6 +404,7 @@ export class Wallet { const { iv, salt, encrypted } = await Wallet.#get(this.#id) const { isUnlocked } = await this.#safe.request({ action: 'unlock', + type: this.#type, password: utf8.toBuffer(password), iv, keySalt: salt, -- 2.47.3