Skip to content

No in-session rekeying: one AES-128-GCM key protects the whole connection lifetime #187

Description

@davvd

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions