Skip to content

wpa::handshake

The 4-way handshake state machine.

This is the station’s half of WPA2-PSK, radio-blind: it is fed each received EAPOL-Key frame and returns what the station should do — send a reply, install keys, or nothing. It never touches a radio, which is what lets it run unchanged on any backend and be driven end to end by a host test.

The exchange, once the PMK is known (from the passphrase and SSID via [crypto::wpa_psk]):

  1. AP → message 1: the ANonce. The station picks its own SNonce, derives the PTK, and replies with message 2 (SNonce + its RSN IE, MIC’d).
  2. AP → message 3: MIC’d, carrying the group key wrapped under the KEK. The station verifies the MIC, unwraps the GTK, and replies with message 4. Both keys are now installed.

The PTK for CCMP is 48 bytes: the key-confirmation key (KCK, MIC), the key-encryption key (KEK, unwraps the GTK), and the temporal key (TK, the data cipher), 16 bytes each.

struct Supplicant { /* private fields */ }

The station’s 4-way handshake.

Holds the secrets for the duration of one handshake: the PMK, the nonces, and the derived PTK. Built once the PMK and both MAC addresses are known.

enum Action<'a> {
None,
Send(&'a [u8]),
Complete { reply: &'a [u8], tk: [u8; 16], gtk: crate::keydata::Gtk },
}

What the caller should do after feeding a frame to [Supplicant::on_eapol].

References: Gtk

enum State {
Idle,
AwaitingThree,
Complete,
Failed,
}

Where the handshake is.

Implements: Clone, Copy, Debug, Eq, PartialEq, StructuralPartialEq

trait Rng {
fn fill(&mut self, buf: &mut [u8])
}

A source of randomness for the SNonce.

The supplicant must not choose its nonce predictably — a reused or guessable SNonce weakens the key. The caller supplies the platform’s entropy (the ESP32 hardware RNG); the handshake only asks for 32 bytes and never for the same nonce twice.