crypto: isolate cipher job errors

Use only errors captured by the cipher job when converting its result.
Unrelated errors on the event loop thread must not turn a successful
operation into a failure or trigger an assertion.

Signed-off-by: Filip Skokan <panva.ip@gmail.com>
Assisted-by: Codex
PR-URL: https://github.com/nodejs/node/pull/66237
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Aviv Keller <me@aviv.sh>
This commit is contained in:
Filip Skokan authored and Node.js GitHub Bot committed 2026-09-25 13:23:06 +00:00
1 parent 8c7346ac63
commit fab9180ee5
2 files changed
+47 -5

No files matched your search

+2 -5
View File
@@ -219,6 +219,7 @@ class CipherJob final : public CryptoJob<CipherTraits> {
WebCryptoCipherMode cipher_mode() const { return cipher_mode_; }
void DoThreadPoolWork() override {
ncrypto::ClearErrorOnReturn clear_error_on_return;
const WebCryptoCipherStatus status =
CipherTraits::DoCipher(
AsyncWrap::env(),
@@ -253,11 +254,7 @@ class CipherJob final : public CryptoJob<CipherTraits> {
Environment* env = AsyncWrap::env();
CryptoErrorStore* errors = CryptoJob<CipherTraits>::errors();
if (errors->Empty())
errors->Capture();
if (out_.size() > 0 || errors->Empty()) {
CHECK(errors->Empty());
if (errors->Empty()) {
*err = v8::Undefined(env->isolate());
*result = out_.ToArrayBuffer(env);
if (result->IsEmpty()) {
@@ -0,0 +1,45 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const { scrypt, scryptSync } = require('crypto');
const { subtle } = globalThis.crypto;
if (typeof scryptSync !== 'function')
common.skip('no scrypt support');
function failScrypt(sync) {
// The failed parameter check leaves an error on the main thread's
// OpenSSL error queue. Cipher jobs must only use their own errors.
assert.throws(() => {
if (sync) {
scryptSync('password', 'salt', 64, { N: 2 ** 17 });
} else {
scrypt('password', 'salt', 64, { N: 2 ** 17 }, common.mustNotCall());
}
}, { code: 'ERR_CRYPTO_INVALID_SCRYPT_PARAMS' });
}
(async () => {
const key = await subtle.importKey(
'raw', new Uint8Array(16), 'AES-GCM', false, ['encrypt', 'decrypt']);
const algorithm = { name: 'AES-GCM', iv: new Uint8Array(12) };
for (const sync of [true, false]) {
for (const byteLength of [0, 16]) {
const plaintext = new Uint8Array(byteLength);
failScrypt(sync);
const ciphertext = await subtle.encrypt(algorithm, key, plaintext);
assert.strictEqual(ciphertext.byteLength, byteLength + 16);
failScrypt(sync);
assert.deepStrictEqual(
new Uint8Array(await subtle.decrypt(algorithm, key, ciphertext)),
plaintext);
}
}
})().then(common.mustCall());