diff --git a/src/jsc/bindings/node/crypto/JSCipherConstructor.cpp b/src/jsc/bindings/node/crypto/JSCipherConstructor.cpp index fef1972bcea1..ff2c86cfc92e 100644 --- a/src/jsc/bindings/node/crypto/JSCipherConstructor.cpp +++ b/src/jsc/bindings/node/crypto/JSCipherConstructor.cpp @@ -189,6 +189,16 @@ JSC_DEFINE_HOST_FUNCTION(constructCipher, (JSC::JSGlobalObject * globalObject, J } } + // OpenSSL 3 caps GCM IVs at 1024 bits (GCM_IV_MAX_SIZE). BoringSSL has no + // such cap, so enforce it here to match Node.js and avoid unbounded GHASH work. + if (cipher.isGcmMode()) { + ASSERT(ivView); + + if (ivView->byteLength() > 128) { + return ERR::CRYPTO_INVALID_IV(scope, globalObject); + } + } + CipherCtxPointer ctx = CipherCtxPointer::New(); if (cipher.isWrapMode()) { diff --git a/test/js/bun/crypto/cipheriv-decipheriv.test.ts b/test/js/bun/crypto/cipheriv-decipheriv.test.ts index af8211d02348..0c1a427fda99 100644 --- a/test/js/bun/crypto/cipheriv-decipheriv.test.ts +++ b/test/js/bun/crypto/cipheriv-decipheriv.test.ts @@ -117,6 +117,19 @@ it("only zero-sized iv or null should be accepted in ECB mode", () => { it("should allow only valid iv lengths in GCM mode", () => { expect(sampleEncryptDecryptGCM("aes-256-gcm", randomBytes(32), randomBytes(1))).toBe(true); expect(sampleEncryptDecryptGCM("aes-256-gcm", randomBytes(32), randomBytes(96))).toBe(true); + expect(sampleEncryptDecryptGCM("aes-256-gcm", randomBytes(32), randomBytes(128))).toBe(true); +}); + +it("should reject GCM IVs longer than 128 bytes", () => { + // Node.js (OpenSSL 3) caps GCM IV length at 1024 bits / 128 bytes. + const invalidIV = { code: "ERR_CRYPTO_INVALID_IV" }; + for (const algo of ["aes-128-gcm", "aes-192-gcm", "aes-256-gcm"] as const) { + const key = randomBytes(algo === "aes-128-gcm" ? 16 : algo === "aes-192-gcm" ? 24 : 32); + expect(() => createCipheriv(algo, key, Buffer.alloc(128))).not.toThrow(); + expect(() => createCipheriv(algo, key, Buffer.alloc(129))).toThrow(expect.objectContaining(invalidIV)); + expect(() => createCipheriv(algo, key, Buffer.alloc(4096))).toThrow(expect.objectContaining(invalidIV)); + expect(() => createDecipheriv(algo, key, Buffer.alloc(129))).toThrow(expect.objectContaining(invalidIV)); + } }); const referencePlaintext = "Out of the mountain of despair, a stone of hope."; diff --git a/test/js/node/test/parallel/test-crypto-cipheriv-decipheriv.js b/test/js/node/test/parallel/test-crypto-cipheriv-decipheriv.js index 095458e7d0b4..6e3777460ac3 100644 --- a/test/js/node/test/parallel/test-crypto-cipheriv-decipheriv.js +++ b/test/js/node/test/parallel/test-crypto-cipheriv-decipheriv.js @@ -208,7 +208,9 @@ assert.throws( // But all other IV lengths should be accepted. const minIvLength = hasOpenSSL3 ? 8 : 1; -const maxIvLength = hasOpenSSL3 ? 64 : 256; +// Bun: BoringSSL has no upper bound, but Bun enforces the OpenSSL 3 cap of +// 128 bytes (1024 bits) for Node.js compatibility. +const maxIvLength = hasOpenSSL3 ? 64 : 129; for (let n = minIvLength; n < maxIvLength; n += 1) { if (isFipsEnabled && n < 12) continue; crypto.createCipheriv('aes-128-gcm', Buffer.alloc(16), Buffer.alloc(n));