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.
This commit is contained in:
Chris Tate
2026-10-21 13:22:56 -05:00
parent 9b70fb5e7b
commit a4d32a8e38
2 changed files with 22 additions and 33 deletions
+5 -32
View File
@@ -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]));
+17 -1
View File
@@ -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<number> {
return new Promise<number>((resolve) =>
@@ -74,12 +86,16 @@ async function main(): Promise<void> {
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();