From: Dmitry Volyntsev Date: Wed, 14 Oct 2026 05:18:37 +0000 (-0700) Subject: WebCrypto: clear OpenSSL errors after a signature mismatch X-Git-Url: https://git.kaiwu.me/?a=commitdiff_plain;h=7c00df7f49c9d8f32f509ea8ef8bcc1e729fea2a;p=njs.git WebCrypto: clear OpenSSL errors after a signature mismatch Previously, crypto.subtle.verify(false) could leave errors for an unrelated TLS handshake or WebCrypto operation. The fix is to clear the queue when returning false, while retaining diagnostics for operation failures. This fixes #1141. --- diff --git a/external/njs_webcrypto_module.c b/external/njs_webcrypto_module.c index ce6e6fe3..c1cbada1 100644 --- a/external/njs_webcrypto_module.c +++ b/external/njs_webcrypto_module.c @@ -4914,6 +4914,11 @@ njs_ext_sign(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, } } else { + if (ret == 0) { + /* A signature mismatch may leave errors in the OpenSSL queue. */ + ERR_clear_error(); + } + njs_value_boolean_set(njs_value_arg(&result), ret != 0); } diff --git a/external/qjs_webcrypto_module.c b/external/qjs_webcrypto_module.c index 8249a39e..13cd41f5 100644 --- a/external/qjs_webcrypto_module.c +++ b/external/qjs_webcrypto_module.c @@ -4895,6 +4895,11 @@ qjs_webcrypto_sign(JSContext *cx, JSValueConst this_val, int argc, } } else { + if (rc == 0) { + /* A signature mismatch may leave errors in the OpenSSL queue. */ + ERR_clear_error(); + } + ret = JS_NewBool(cx, rc != 0); } diff --git a/test/webcrypto/sign.t.mjs b/test/webcrypto/sign.t.mjs index 2999569f..091f2989 100644 --- a/test/webcrypto/sign.t.mjs +++ b/test/webcrypto/sign.t.mjs @@ -1,5 +1,5 @@ /*--- -includes: [compatFs.js, compatBuffer.js, compatWebcrypto.js, runTsuite.js, webCryptoUtils.js] +includes: [compatFs.js, compatBuffer.js, compatNjs.js, compatWebcrypto.js, runTsuite.js, webCryptoUtils.js] flags: [async] ---*/ @@ -153,6 +153,55 @@ function p(args, default_opts) { return params; } +async function decrypt_error(key) { + try { + await crypto.subtle.decrypt( + {name: "AES-CBC", iv: new Uint8Array(16)}, key, + new Uint8Array(16)); + } catch (e) { + return e.toString(); + } + + throw Error("decrypt accepted invalid padding"); +} + +async function test_verify_error_queue() { + let pair = await crypto.subtle.generateKey( + {name: "RSA-PSS", modulusLength: 2048, hash: "SHA-256", + publicExponent: new Uint8Array([1, 0, 1])}, + false, ["sign", "verify"]); + let key = await crypto.subtle.importKey("raw", new Uint8Array(16), + "AES-CBC", false, ["decrypt"]); + let options = {name: "RSA-PSS", saltLength: 32}; + let data = new Uint8Array([1, 2, 3]); + let signature = await crypto.subtle.sign(options, pair.privateKey, data); + + /* Drain errors left by preceding tests before taking the baseline. */ + await decrypt_error(key); + let expected = await decrypt_error(key); + + data[0] ^= 1; + let verified = await crypto.subtle.verify(options, pair.publicKey, + signature, data); + if (verified !== false) { + throw Error("verify accepted an invalid signature"); + } + + let actual = await decrypt_error(key); + if (actual !== expected) { + throw Error(`verify polluted OpenSSL errors: ${actual} != ${expected}`); + } + + return "SUCCESS"; +} + +let verify_error_queue_tsuite = { + name: "verify OpenSSL error queue", + skip: () => !has_njs() || !has_webcrypto(), + T: test_verify_error_queue, + tests: [{}], +}; + let hmac_tsuite = { name: "HMAC sign", skip: () => (!has_buffer() || !has_webcrypto()), @@ -353,6 +402,7 @@ run([ hmac_tsuite, rsassa_pkcs1_v1_5_tsuite, rsa_pss_tsuite, - ecdsa_tsuite + ecdsa_tsuite, + verify_error_queue_tsuite ]) .then($DONE, $DONE);