From b942daf7f99c9414334e3a9f689786e24b92a9af Mon Sep 17 00:00:00 2001 From: Bishop Bettini Date: Mon, 20 Jul 2026 09:19:03 -0400 Subject: [PATCH] fix:no error message on wrong password --- packages/kdbx/src/crypto.ts | 15 ++++++++++++--- packages/kdbx/tests/kdbx.test.ts | 18 ++++++++++++++---- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/packages/kdbx/src/crypto.ts b/packages/kdbx/src/crypto.ts index e19d9c7..c032ae6 100644 --- a/packages/kdbx/src/crypto.ts +++ b/packages/kdbx/src/crypto.ts @@ -82,9 +82,18 @@ export async function aesCbcDecrypt( ): Promise { const subtle = kx_getCrypto().subtle; const cryptoKey = await subtle.importKey('raw', kx_buf(key), 'AES-CBC', false, ['decrypt']); - return new Uint8Array( - await subtle.decrypt({ name: 'AES-CBC', iv: kx_buf(iv) }, cryptoKey, kx_buf(data)), - ); + try { + return new Uint8Array( + await subtle.decrypt({ name: 'AES-CBC', iv: kx_buf(iv) }, cryptoKey, kx_buf(data)), + ); + } catch { + // WebCrypto throws a DOMException with an empty message on a PKCS#7 + // padding failure (deliberately, to avoid a padding-oracle side + // channel) — and a wrong key almost always produces invalid padding, so + // this is the ordinary "wrong password" case for KDBX 3.1 files, not a + // rare corruption edge case. Give callers something to show the user. + throw new Error('AES-CBC decryption failed (wrong credentials or corrupt file)'); + } } /** diff --git a/packages/kdbx/tests/kdbx.test.ts b/packages/kdbx/tests/kdbx.test.ts index 8731029..5227610 100644 --- a/packages/kdbx/tests/kdbx.test.ts +++ b/packages/kdbx/tests/kdbx.test.ts @@ -103,16 +103,26 @@ for (const config of CONFIGS) { }); } -test('wrong credentials are rejected', async () => { +test('wrong credentials are rejected, with a message the UI can show', async () => { const kdbx = await Kdbx.create(Credentials.fromPassword('right'), options({ version: 4 })); const saved = await kdbx.save(); - await assert.rejects(() => Kdbx.load(saved, Credentials.fromPassword('wrong'))); + await assert.rejects( + () => Kdbx.load(saved, Credentials.fromPassword('wrong')), + (err: Error) => err.message.length > 0, + ); }); -test('wrong credentials are rejected (KDBX 3.1)', async () => { +test('wrong credentials are rejected, with a message the UI can show (KDBX 3.1)', async () => { + // Regression test: KDBX 3.1 decrypts the outer AES-CBC payload before any + // "wrong credentials" check runs, and WebCrypto throws an empty-message + // DOMException on the resulting padding failure — page.ts's unlock screen + // was rendering that empty message as a blank error bar. const kdbx = await Kdbx.create(Credentials.fromPassword('right'), options({ version: 3 })); const saved = await kdbx.save(); - await assert.rejects(() => Kdbx.load(saved, Credentials.fromPassword('wrong'))); + await assert.rejects( + () => Kdbx.load(saved, Credentials.fromPassword('wrong')), + (err: Error) => err.message.length > 0, + ); }); test('multiple protected fields decrypt in document order', async () => {