From f7495e221b88b9dd89eaf45cea46550023199635 Mon Sep 17 00:00:00 2001 From: Dmitry Volyntsev Date: Wed, 14 Oct 2026 23:06:08 -0700 Subject: [PATCH] WebCrypto: return false for wrong-length ECDSA signatures ECDSA signatures of an invalid length cause verify() to throw instead of returning false. njs passes an empty DER signature to EVP_PKEY_verify(), while QuickJS rejects the length during conversion. The njs behavior dates back to 86c202c7 (0.7.7); QuickJS rejects these signatures since 75ca26f8 (0.8.10). Treat a length mismatch as an invalid signature, as required by WebCrypto ECDSA Verify, while preserving diagnostics for conversion failures. Test all three supported curves with invalid lengths and a valid signature. --- external/njs_webcrypto_module.c | 16 ++++++++++-- external/qjs_webcrypto_module.c | 16 +++++++++--- test/webcrypto/sign.t.mjs | 45 +++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 5 deletions(-) diff --git a/external/njs_webcrypto_module.c b/external/njs_webcrypto_module.c index c1cbada1..547917a4 100644 --- a/external/njs_webcrypto_module.c +++ b/external/njs_webcrypto_module.c @@ -4552,8 +4552,13 @@ njs_convert_p1363_to_der(njs_vm_t *vm, EVP_PKEY *pkey, u_char *p1363, n = njs_ec_rs_size(pkey); - if (njs_slow_path(n == 0 || p1363_len != 2 * n)) { - goto fail; + if (njs_slow_path(n == 0)) { + njs_webcrypto_error(vm, "cannot determine ECDSA signature length"); + return NJS_ERROR; + } + + if (p1363_len != 2 * n) { + return NJS_DECLINED; } ec_sig = ECDSA_SIG_new(); @@ -4887,6 +4892,11 @@ njs_ext_sign(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, ret = njs_convert_p1363_to_der(vm, key->u.a.pkey, sig.start, sig.length, &sig.start, &sig.length); + if (ret == NJS_DECLINED) { + ret = 0; + goto verify_done; + } + if (njs_slow_path(ret != NJS_OK)) { goto fail; } @@ -4899,6 +4909,8 @@ njs_ext_sign(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, } } + verify_done: + njs_evp_md_ctx_free(mctx); EVP_PKEY_CTX_free(pctx); diff --git a/external/qjs_webcrypto_module.c b/external/qjs_webcrypto_module.c index 13cd41f5..4a70b942 100644 --- a/external/qjs_webcrypto_module.c +++ b/external/qjs_webcrypto_module.c @@ -4529,12 +4529,15 @@ qjs_convert_p1363_to_der(JSContext *cx, EVP_PKEY *pkey, u_char *p1363, n = qjs_ec_rs_size(pkey); - if (n == 0 || p1363_len != 2 * n) { - JS_ThrowTypeError(cx, "invalid ECDSA signature length %zu != %u", - p1363_len, 2 * n); + if (n == 0) { + qjs_webcrypto_error(cx, "cannot determine ECDSA signature length"); return -1; } + if (p1363_len != 2 * n) { + return 1; + } + ec_sig = ECDSA_SIG_new(); if (ec_sig == NULL) { JS_ThrowOutOfMemory(cx); @@ -4862,6 +4865,11 @@ qjs_webcrypto_sign(JSContext *cx, JSValueConst this_val, int argc, rc = qjs_convert_p1363_to_der(cx, key->u.a.pkey, sig.start, sig.length, &sig.start, &sig.length); + if (rc > 0) { + rc = 0; + goto verify_done; + } + if (rc < 0) { goto fail; } @@ -4878,6 +4886,8 @@ qjs_webcrypto_sign(JSContext *cx, JSValueConst this_val, int argc, goto fail; } + verify_done: + js_free(cx, dst); } diff --git a/test/webcrypto/sign.t.mjs b/test/webcrypto/sign.t.mjs index 091f2989..9dec51fd 100644 --- a/test/webcrypto/sign.t.mjs +++ b/test/webcrypto/sign.t.mjs @@ -398,11 +398,56 @@ let ecdsa_tsuite = { exception: "TypeError: Unsupported key usage for a ECDSA key" }, ]}; +async function test_ecdsa_signature_length(params) { + let pair = await crypto.subtle.generateKey( + {name: "ECDSA", namedCurve: params.curve}, false, ["sign", "verify"]); + let options = {name: "ECDSA", hash: "SHA-256"}; + let data = new Uint8Array([1, 2, 3]); + let signature = await crypto.subtle.sign(options, pair.privateKey, data); + + if (signature.byteLength !== params.length) { + throw Error("unexpected ECDSA signature length"); + } + + let lengths = [0, 3, params.length - 1, params.length + 1]; + for (let i = 0; i < lengths.length; i++) { + let length = lengths[i]; + let invalid = new Uint8Array(length); + invalid.set(new Uint8Array(signature).subarray(0, length)); + + let verified = await crypto.subtle.verify(options, pair.publicKey, + invalid, data); + if (verified !== false) { + throw Error(`ECDSA accepted a signature of length ${length}`); + } + } + + let verified = await crypto.subtle.verify(options, pair.publicKey, + signature, data); + if (verified !== true) { + throw Error("ECDSA rejected a valid signature"); + } + + return "SUCCESS"; +} + +let ecdsa_signature_length_tsuite = { + name: "ECDSA signature length", + skip: () => !has_webcrypto(), + T: test_ecdsa_signature_length, + tests: [ + {curve: "P-256", length: 64}, + {curve: "P-384", length: 96}, + {curve: "P-521", length: 132}, + ], +}; + run([ hmac_tsuite, rsassa_pkcs1_v1_5_tsuite, rsa_pss_tsuite, ecdsa_tsuite, + ecdsa_signature_length_tsuite, verify_error_queue_tsuite ]) .then($DONE, $DONE); -- 2.47.3