From a4d32a8e38fd765dc688ff057110c8ace10b7d3a Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Wed, 21 Oct 2026 13:22:56 -0500 Subject: [PATCH] Use the default TLS version bound for null minVersion and maxVersion in tls.createServer tls.createServer validated minVersion and maxVersion with its own loop, which threw ERR_TLS_INVALID_PROTOCOL_VERSION for null. It now uses the same check as tls.connect, where null and undefined select the default bound, as in Node. --- packages/runtime/src/scr_tls.c | 37 ++++-------------------- tests/corpus/6722-tls-min-max-version.ts | 18 +++++++++++- 2 files changed, 22 insertions(+), 33 deletions(-) diff --git a/packages/runtime/src/scr_tls.c b/packages/runtime/src/scr_tls.c index 59f3c2c0a..526dd7760 100644 --- a/packages/runtime/src/scr_tls.c +++ b/packages/runtime/src/scr_tls.c @@ -1813,38 +1813,11 @@ static bool scr_tls_opts_validate(const ScrDyn *opts) { return false; } } - static const char *const VERSION_OPTS[] = { "minVersion", "maxVersion", NULL }; - for (size_t i = 0; VERSION_OPTS[i] != NULL; i++) { - const ScrDyn *v = scr_dyn_obj_get(opts, VERSION_OPTS[i], strlen(VERSION_OPTS[i])); - if (v == NULL || v->kind == SCR_DYN_UNDEF) continue; - bool valid = false; - if (v->kind == SCR_DYN_STR) { - static const char *const KNOWN[] = { "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3", NULL }; - for (size_t k = 0; KNOWN[k] != NULL; k++) { - if (v->v.str->len == strlen(KNOWN[k]) && memcmp(v->v.str->data, KNOWN[k], v->v.str->len) == 0) { - valid = true; - break; - } - } - } - if (!valid) { - /* %j: strings render JSON-quoted, everything else inspect-lite. */ - char rendered[96]; - if (v->kind == SCR_DYN_STR) { - snprintf(rendered, sizeof rendered, "\"%.*s\"", - (int)(v->v.str->len < 80 ? v->v.str->len : 80), v->v.str->data); - } else { - /* %j over non-strings: JSON.stringify's scalar renderings. */ - char lite[64]; - snprintf(rendered, sizeof rendered, "%s", scr_dyn_inspect_lite(v, lite, sizeof lite)); - } - char msg[192]; - int len = snprintf(msg, sizeof msg, "%s is not a valid %s TLS protocol version", - rendered, VERSION_OPTS[i][2] == 'n' ? "minimum" : "maximum"); - scr_throw_error_msg_code(SCR_ERR_TYPE, msg, (size_t)len, "ERR_TLS_INVALID_PROTOCOL_VERSION"); - return false; - } - } + /* Node's toV: null and undefined select the default bound. */ + int version; + if (!scr_tls_version_dyn(scr_dyn_obj_get(opts, "minVersion", 10), "minimum", 12, &version) || + !scr_tls_version_dyn(scr_dyn_obj_get(opts, "maxVersion", 10), "maximum", 13, &version)) + return false; static const char *const NUM_OPTS[] = { "handshakeTimeout", "keepAliveInitialDelay", NULL }; for (size_t i = 0; NUM_OPTS[i] != NULL; i++) { const ScrDyn *v = scr_dyn_obj_get(opts, NUM_OPTS[i], strlen(NUM_OPTS[i])); diff --git a/tests/corpus/6722-tls-min-max-version.ts b/tests/corpus/6722-tls-min-max-version.ts index 07d01867d..70cddae52 100644 --- a/tests/corpus/6722-tls-min-max-version.ts +++ b/tests/corpus/6722-tls-min-max-version.ts @@ -4,7 +4,8 @@ // ERR_SSL_TLSV1_ALERT_PROTOCOL_VERSION, a client range with neither TLSv1.2 // nor TLSv1.3 fails with ERR_SSL_NO_PROTOCOLS_AVAILABLE once the handshake // starts, and an invalid version or a secureProtocol conflict throws Node's -// TypeError from tls.connect. +// TypeError from tls.connect and tls.createServer. null selects the default +// bound, as undefined does. import * as tls from "node:tls"; import { readFileSync } from "node:fs"; @@ -16,6 +17,17 @@ const key = readFileSync(dir + "localhost-key.pem"); const upTo12 = tls.createServer({ cert, key, maxVersion: "TLSv1.2" }, (s) => s.end(`server:${s.getProtocol()}`)); const from13 = tls.createServer({ cert, key, minVersion: "TLSv1.3" }, (s) => s.end(`server:${s.getProtocol()}`)); const any = tls.createServer({ cert, key }, (s) => s.end(`server:${s.getProtocol()}`)); +const nullBounds = tls.createServer({ cert, key, minVersion: null as any, maxVersion: null as any }, (s) => + s.end(`server:${s.getProtocol()}`), +); +for (const maxVersion of [3, "TLSv9"]) { + try { + tls.createServer({ cert, key, maxVersion: maxVersion as any }); + } catch (e) { + const err = e as NodeJS.ErrnoException; + console.log("createServer", err.name, err.code, err.message); + } +} function listen(server: tls.Server): Promise { return new Promise((resolve) => @@ -74,12 +86,16 @@ async function main(): Promise { await attempt("max-1.1", pAny, { maxVersion: "TLSv1.1" }); await attempt("min-above-max", pAny, { minVersion: "TLSv1.3", maxVersion: "TLSv1.2" }); await attempt("null-min", pAny, { minVersion: null }); + const pNull = await listen(nullBounds); + await attempt("null-server", pNull, {}); + await attempt("null-both", pNull, { minVersion: null, maxVersion: null }); await attempt("invalid-min", pAny, { minVersion: "TLSv9" }); await attempt("invalid-max", pAny, { maxVersion: 3 }); await attempt("conflict", pAny, { minVersion: "TLSv1.2", secureProtocol: "TLSv1_2_method" }); upTo12.close(); from13.close(); any.close(); + nullBounds.close(); } void main();