diff --git a/libs/integrations/ksef/src/infrastructure/crypto/__tests__/aes-cipher.spec.ts b/libs/integrations/ksef/src/infrastructure/crypto/__tests__/aes-cipher.spec.ts index 7f75dccdd..ede6046fb 100644 --- a/libs/integrations/ksef/src/infrastructure/crypto/__tests__/aes-cipher.spec.ts +++ b/libs/integrations/ksef/src/infrastructure/crypto/__tests__/aes-cipher.spec.ts @@ -31,9 +31,27 @@ describe('aes-cipher', () => { expect(() => encryptAesCbc('x', key, new Uint8Array(8))).toThrow(KsefSessionCryptoException); }); - it('should throw KsefSessionCryptoException when decrypting with the wrong key', () => { - const ciphertext = encryptAesCbc('secret', key, iv); + it('should never recover the plaintext when decrypting with the wrong key', () => { + // CBC has no key verification: a wrong key usually fails PKCS#7 padding + // validation (throws), but ~0.4% of random keys yield accidentally valid + // padding and return garbage instead. Both outcomes are legal (#1538). + const plaintext = 'secret'; + const ciphertext = encryptAesCbc(plaintext, key, iv); const wrongKey = new Uint8Array(randomBytes(KSEF_AES_KEY_BYTES)); - expect(() => decryptAesCbc(ciphertext, wrongKey, iv)).toThrow(KsefSessionCryptoException); + let result: string | undefined; + try { + result = decryptAesCbc(ciphertext, wrongKey, iv); + } catch (err) { + expect(err).toBeInstanceOf(KsefSessionCryptoException); + return; + } + expect(result).not.toBe(plaintext); + }); + + it('should throw KsefSessionCryptoException when the ciphertext length is not a multiple of the block size', () => { + // decipher.final() always throws on a truncated ciphertext, so this covers + // the wrapping path of decryptAesCbc deterministically. + const truncated = encryptAesCbc('secret', key, iv).slice(0, -1); + expect(() => decryptAesCbc(truncated, key, iv)).toThrow(KsefSessionCryptoException); }); });