From 5f25978c44b8c3a500c99252a7f942f987cbc4b6 Mon Sep 17 00:00:00 2001 From: Pranav Jain Date: Thu, 3 Sep 2026 14:24:07 -0400 Subject: [PATCH] fix(sdk-api): route browser v1 decrypt straight to SJCL crypto-browserify's AES-CCM is reported broken in real browsers, so skip the native attempt there and go directly to sjcl.decrypt after the existing iteration-cap check. Also drops the unused, unreviewed WebCrypto CCM candidate (decryptV1WebCrypto.ts) that had no caller. TICKET: WCN-2576 --- modules/sdk-api/src/encrypt.ts | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/modules/sdk-api/src/encrypt.ts b/modules/sdk-api/src/encrypt.ts index 0e285e4b05..36695eb691 100644 --- a/modules/sdk-api/src/encrypt.ts +++ b/modules/sdk-api/src/encrypt.ts @@ -1,7 +1,7 @@ import * as sjcl from '@bitgo/sjcl'; import { randomBytes } from 'crypto'; -import { decryptV1 } from './decryptV1'; +import { decryptV1, parseV1Envelope } from './decryptV1'; import { decryptV2, encryptV2 } from './encryptV2'; /** @@ -76,6 +76,15 @@ function isIterCapViolation(err: unknown): boolean { return err instanceof Error && /iter:\s*expected integer|iter out of range/i.test(err.message); } +/** + * True in window and worker runtimes. Used to route browser v1 decrypt + * straight to SJCL instead of through the Node-oriented `native` path, whose + * `crypto-browserify` AES-CCM has been reported not to work in a real browser. + */ +function isBrowserRuntime(): boolean { + return typeof window !== 'undefined' || typeof self !== 'undefined'; +} + /** * v1 decrypt with an SJCL safety net. * @@ -98,6 +107,15 @@ export async function decryptV1WithFallback( ciphertext: string, native: (pw: string, ct: string) => Promise = decryptV1 ): Promise { + if (isBrowserRuntime()) { + try { + parseV1Envelope(ciphertext); + } catch (parseErr) { + if (isIterCapViolation(parseErr)) throw parseErr; + // Any other parse rejection (e.g. absent `v`) is a shape SJCL still accepts. + } + return sjcl.decrypt(password, ciphertext); + } try { return await native(password, ciphertext); } catch (nativeErr) {