After the TLS 1.3 handshake the node derives its application keys exactly once, in deriveAppKeys (node/internal/server/serverhandshake.go:214), and each direction's AEAD nonce is just a monotonic 64-bit counter XORed into a fixed IV, incremented forever and never rotated (node/internal/server/record.go:78). Neither client ever rotates either: they never send a TLS KeyUpdate and ignore every post-handshake handshake record except Finished (ios/APNExtension/Handshake.swift:241; the Android Handshake.kt mirrors it). The node could not honor a KeyUpdate even if one arrived — secureConn.fill accepts only contentTypeApplicationData and silently drops anything else (node/internal/server/secureconn.go:147).
The result is that a single AES-128-GCM key, derived at connect time, protects the entire session — which for a mobile tunnel can be hours of traffic — with no forward-secrecy refresh and no post-compromise security. WireGuard treats bounded key lifetime as a core property: keys rotate roughly every 120s (RekeyAfterTime) or after 2^60 messages, with a hard stop at 180s / 2^64-2^13-1 messages forcing a fresh handshake (wireguard-go device/constants.go). We have no analog.
Implement TLS 1.3 KeyUpdate (RFC 8446 §4.6.3) on a timer or byte-count (e.g. every few minutes or every N GB): have both clients emit KeyUpdate and rotate their write keys, and change secureConn.fill (secureconn.go:147) to process an inbound post-handshake KeyUpdate record and rotate the read key rather than drop it. That bounds each key's exposure without paying for a full reconnect. Filing per a WireGuard comparison review.
After the TLS 1.3 handshake the node derives its application keys exactly once, in
deriveAppKeys(node/internal/server/serverhandshake.go:214), and each direction's AEAD nonce is just a monotonic 64-bit counter XORed into a fixed IV, incremented forever and never rotated (node/internal/server/record.go:78). Neither client ever rotates either: they never send a TLS KeyUpdate and ignore every post-handshake handshake record except Finished (ios/APNExtension/Handshake.swift:241; the AndroidHandshake.ktmirrors it). The node could not honor a KeyUpdate even if one arrived —secureConn.fillaccepts onlycontentTypeApplicationDataand silently drops anything else (node/internal/server/secureconn.go:147).The result is that a single AES-128-GCM key, derived at connect time, protects the entire session — which for a mobile tunnel can be hours of traffic — with no forward-secrecy refresh and no post-compromise security. WireGuard treats bounded key lifetime as a core property: keys rotate roughly every 120s (
RekeyAfterTime) or after2^60messages, with a hard stop at 180s /2^64-2^13-1messages forcing a fresh handshake (wireguard-godevice/constants.go). We have no analog.Implement TLS 1.3 KeyUpdate (RFC 8446 §4.6.3) on a timer or byte-count (e.g. every few minutes or every N GB): have both clients emit KeyUpdate and rotate their write keys, and change
secureConn.fill(secureconn.go:147) to process an inbound post-handshake KeyUpdate record and rotate the read key rather than drop it. That bounds each key's exposure without paying for a full reconnect. Filing per a WireGuard comparison review.