worker: use TLS 1.3 transparent rekey on TLS 1.3 CSTP sessions

TLS 1.3 has no renegotiation and provides transparent rekeys but this
was not used by the server. Instead rekey-method=ssl was silently
downgraded to new-tunnel, causing a full tunnel/TUN-device rebuild (and
a brief data-path interruption) on every rekey whenever a client
negotiated TLS 1.3.

This commit simplifies that handling by taking advantage of TLS 1.3's own
rekey mechanism instead: the server now performs the "ssl" rekey on
TLS 1.3 sessions via the standard TLS 1.3 KeyUpdate message.

TLS <= 1.2 rekey behavior (client-driven rehandshake, gated on RFC
5746 safe renegotiation) is unchanged.

Resolves: #745

Signed-off-by: Nikos Mavrogiannopoulos <n.mavrogiannopoulos@gmail.com>
This commit is contained in:
Nikos Mavrogiannopoulos
2026-07-28 08:07:07 +02:00
parent 76987ccb82
commit 9e71c1b8b6
9 changed files with 481 additions and 2 deletions
+37
View File
@@ -613,6 +613,43 @@ response advertises `X-CSTP-Rekey-Method: ssl` and
`X-CSTP-Rekey-Time: <N-derived value>`; a rekey is triggered at approximately
`rekey_time` (with jitter — `FUZZ(WSRCONFIG(ws)->rekey_time, 30, rnd)` at
`src/worker-vpn.c:2111`).
*TLS ≤ 1.2*: rekey is an in-place rehandshake, gated on RFC 5746 safe
renegotiation (`gnutls_safe_renegotiation_status()`,
`src/worker-vpn.c:2415-2419`), falling back to `new-tunnel` only for peers
that lack it.
*TLS 1.3+*: TLS 1.3 has no renegotiation, so the server performs the whole
rekey itself, with no client involvement: it advertises
`X-CSTP-Rekey-Method: none` (not `ssl`) and, from `periodic_check()` at
approximately `rekey_time`, unilaterally issues a TLS 1.3 `KeyUpdate`
(`gnutls_session_key_update(session, GNUTLS_KU_PEER)`, GnuTLS ≥ 3.6.3, in
`cstp_transparent_rekey_update()`, gated by
`cstp_transparent_rekey_capable()`). There is no tunnel/TUN-device rebuild
and no client-visible interruption — any TLS 1.3 peer accepts an
unsolicited KeyUpdate transparently at the record layer (RFC 8446 §4.6.3),
regardless of the CSTP header value.
`none` is required rather than `ssl` on TLS 1.3 because both of
OpenConnect's TLS backends mishandle a client-driven rehandshake on an
already-established TLS 1.3 session (GnuTLS backend: session torn down with
an "illegal parameter" alert; OpenSSL backend: forced `new-tunnel`
reconnect) — the exact disruption this requirement exists to avoid. Full
analysis in the comment above `cstp_transparent_rekey_capable()` in
`src/worker-vpn.c`.
The rekey is atomic: `gnutls_session_key_update()` is retried on
`GNUTLS_E_AGAIN`/`GNUTLS_E_INTERRUPTED` for up to 30 seconds; if it has not
succeeded by then, or fails for any other reason, the worker ends the
session (`exit_worker_reason(ws, REASON_ERROR)`) rather than leave a rekey
partially pending.
Tested by `tests/test-rekey-tls13` (TLS 1.3: one tun-device assignment for
the session, i.e. no rebuild; `TLS 1.3 session keys refreshed` logged;
tunnel pings succeed before and after the rekey window) and
`tests/test-rekey-tls12` (TLS 1.2 negative case: legacy rehandshake
completes, no TLS 1.3 KeyUpdate logged). Both connect with `--no-dtls` so
the assertions exercise CSTP/TLS, not DTLS.
**Divergence**: both `ssl` and `new-tunnel` are implemented (not just
advertised), so this is MAJORITY rather than EXTENSION; classified MAJORITY
(not UNIVERSAL) only because the *value* `rekey-time` and the *jitter* (`FUZZ`,
+4 -1
View File
@@ -490,7 +490,10 @@ rekey-time = 172800
# ReKey method
# Valid options: ssl, new-tunnel
# ssl: Will perform an efficient rehandshake on the channel allowing
# a seamless connection during rekey.
# a seamless connection during rekey. On TLS 1.2 and earlier this is
# an in-place rehandshake; on TLS 1.3 (which has no renegotiation)
# it is performed transparently via a standard TLS 1.3 KeyUpdate,
# with no tunnel rebuild or client-visible interruption either way.
# new-tunnel: Will instruct the client to discard and re-establish the channel.
# Use this option only if the connecting clients have issues with the ssl
# option.