diff --git a/src/crypto/crypto_cipher.cc b/src/crypto/crypto_cipher.cc index 81ba818a2dc7d3..2685f5ea0bea99 100644 --- a/src/crypto/crypto_cipher.cc +++ b/src/crypto/crypto_cipher.cc @@ -901,6 +901,14 @@ bool CipherBase::Final(std::unique_ptr* out) { if (kind_ == kDecipher && IsSupportedAuthenticatedMode(ctx_.get())) MaybePassAuthTagToOpenSSL(); + // OpenSSL v1.x doesn't verify the presence of the auth tag so do + // it ourselves, see https://github.com/nodejs/node/issues/45874. + if (OPENSSL_VERSION_NUMBER < 0x30000000L && kind_ == kDecipher && + NID_chacha20_poly1305 == EVP_CIPHER_CTX_nid(ctx_.get()) && + auth_tag_state_ != kAuthTagPassedToOpenSSL) { + return false; + } + // In CCM mode, final() only checks whether authentication failed in update(). // EVP_CipherFinal_ex must not be called and will fail. bool ok; diff --git a/test/parallel/test-crypto-authenticated.js b/test/parallel/test-crypto-authenticated.js index 162b451c5b459c..d358f6b63c0e9f 100644 --- a/test/parallel/test-crypto-authenticated.js +++ b/test/parallel/test-crypto-authenticated.js @@ -786,3 +786,34 @@ for (const test of TEST_CASES) { assert.strictEqual(plaintext.toString('hex'), testCase.plain); } } + +// https://github.com/nodejs/node/issues/45874 +{ + const rfcTestCases = TEST_CASES.filter(({ algo, tampered }) => { + return algo === 'chacha20-poly1305' && tampered === false; + }); + assert.strictEqual(rfcTestCases.length, 1); + + const [testCase] = rfcTestCases; + const key = Buffer.from(testCase.key, 'hex'); + const iv = Buffer.from(testCase.iv, 'hex'); + const aad = Buffer.from(testCase.aad, 'hex'); + const opt = { authTagLength: 16 }; + + const cipher = crypto.createCipheriv('chacha20-poly1305', key, iv, opt); + const ciphertext = Buffer.concat([ + cipher.setAAD(aad).update(testCase.plain, 'hex'), + cipher.final(), + ]); + const authTag = cipher.getAuthTag(); + + assert.strictEqual(ciphertext.toString('hex'), testCase.ct); + assert.strictEqual(authTag.toString('hex'), testCase.tag); + + const decipher = crypto.createDecipheriv('chacha20-poly1305', key, iv, opt); + decipher.setAAD(aad).update(ciphertext); + + assert.throws(() => { + decipher.final(); + }, /Unsupported state or unable to authenticate data/); +}