Repository navigation
Conversation
toyos-wlan-handshake (system/wlan/handshake) is the supplicant's side of the 4-way and group key handshakes (IEEE Std 802.11-2020 §12.7) under AKM 2 with CCMP-128, and BIP-CMAC-128's IGTK when management frame protection is negotiated. It takes the PMK and what the association settled, answers each EAPOL-Key frame with the frame to send and the keys to install after it, and refuses every hostile frame by name: - a Key Replay Counter not above the last verified one, message 1 included; - a resent message 1 is answered under its first SNonce; after the handshake, a message 1 rekeys under a fresh one; - a retransmitted message 3 is answered and installs nothing, and a group key is installed only when it is not the key under its ID; - message 3's RSNE and RSNXE must be the beacon's, byte for byte; - GTK key ID 0, an IGTK outside 4 and 5, a MIC mismatch, failed unwraps and every length that runs past its frame. lib/crypto publishes ct::equal and Secret<N> (with Zeroable, which bounds it) unchanged, with new/expose/expose_mut: the MIC comparison and the owners of the PTK and group keys. Its tests gain IEEE 802.11 Annex J's four PRF vectors and two more pass-phrase-to-PSK ones. The tests drive the supplicant with an authenticator written from the standard on ring's HMAC-SHA1 and the aes crate's block cipher, so the two sides share no crypto code. SAE, AES-CMAC, PSK-SHA256 and Transition Disable are W2b, which waits for P6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The one RFC 6070 PBKDF2-HMAC-SHA1 case at 4096 iterations whose password and salt a pass-phrase and SSID can be is a test of derive_pmk. The track's W2 entry says what W2a built, that the captured handshake is still owed, and what W2b waits on P6 for. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two joins wpa_supplicant v2.10 recorded on the T14 bench, as access point (mode=2) and station over two mac80211_hwsim radios: plain PSK, and PMF required. Each fixture is six records of the radiotap capture, unmodified: the Probe Response, the Association Request and the four EAPOL-Key frames. tests/capture.rs takes the RSNEs from the first two. Given message 1 and message 2's SNonce, the supplicant's message 2 must equal the captured one; from message 3, its message 4 must too. The TK, GTK and IGTK must be the ones the station's -K log printed. Swapping the nonces' order in the PTK derivation reds both. No group key handshake is recorded: v2.10's access point parsed group_rekey and wpa_ptk_rekey and never rekeyed. The 802.11 readings the crate cites are listed for a check against the text in issues/the-wlan-handshakes-readings-of-802-11-are-unchecked-against-its-text.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review r1 of #1130 at af13f26Architectural assessmentThe split is right.
Net change: about 900 lines of production code (crate src about 900, crypto +25), about 1,130 lines of tests and two 1.3 KB fixtures. Security properties, traced by hand:
Evidence, verified:
Oracle independence:
Required
Optional
SEND BACK |
…he comments say what holds - The clause-check issue gains seven rows: the Ack, Request and Error rule; message 1's, message 3's and group message 1's flags; Key Length 16; Key RSC and IPN as the last packet number sent; the MFP negotiation. - UnknownMessage's reason names every combination it refuses. - The pending handshake lasts until a message 3 is accepted, not until one verifies: a message 3 refused after its MIC verified accepts nothing, and a following message 1 keeps the SNonce. The behaviour stays: no PTK from that pending handshake was installed, so no (ANonce, SNonce) pair is installed twice, and a verified refusal is the caller's reason to leave the network, after which a new Supplicant draws a new SNonce. - The MIC field's presence is an expect, not a silent skip; ct::equal's doc says what it does, not a timing guarantee; KEY_DATA_MAX's bound is the MSDU's; the dev-dependency comment and the track bullet are cut to what is true. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Review r1 fixed at 1679ef9 (rebased onto main a2a4548). Required
Optional, taken
Optional, declined
Checks
|
The second capture's station set wpa_ptk_rekey=6, so each run holds a
join and two rekeys it asked for. rekey-{psk,pmf}.txt carry the
addresses and RSNEs from the run's radio capture, then every EAPOL-Key
frame and key the station's -dd -K log printed. Rekeys are
CCMP-protected on air, so the log is their record. Each of the
supplicant's six answers must be the station's own, skipping the
Requests the station sent on its own. Each TK must be its logged PTK's,
and the group keys are handed out at the join alone, as the station
logged "Not reinstalling already in-use GTK" and "... IGTK" at each
rekey. Reinstalling the GTK, or reusing the installed handshake's
SNonce in a rekey, reds both tests.
The group key handshake stays unrecorded: v2.10's wpa_cli answered
"Unknown command 'REKEY_GTK'".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Rekey fixtures folded in at 26652fe, on review r1's fixes (1679ef9, reply above).
Evidence: https://github.com/ToyOSOrg/evidence/blob/2abd65ded9086596565f9f694220ea5435755310/w2a-r4/README.md |
Problem
W2 (
issues/there-is-no-wifi.md) is the handshakeswlanruns. Its SAE, AES-CMAC and PSK-SHA256 parts need P6, which the owner has paused. Its WPA2-Personal part needs only whatlib/cryptoalready has. W4 depends on W2, so W2a, the WPA2 part, is built now.Resulting behavior
toyos-wlan-handshake(system/wlan/handshake, a pure host-tested crate) is the supplicant side of two handshakes, sans-IO:It covers AKM 2 (PSK) with CCMP-128, plus the BIP-CMAC-128 IGTK when management frame protection (PMF) is negotiated.
Supplicant::new(pmk, &Join)takes the PMK and what the association settled: AA, SPA, the beacon's RSNE/RSNXE and the station's own. An association it cannot key is refused asUnjoinable, by name.receive(frame, nonce)answers one EAPOL-Key frame. It returns aStep: the reply frame, plus the pairwise, group and integrity keys to install after sending it. Keys are held intoyos_crypto::Secret, which erases them on drop.Refused):lib/cryptopublishesct::equalandSecret<N>unchanged, as ruled (option a).Secret<N>gainsnew,exposeandexpose_mut.Zeroablebecomes public because it boundsSecret.Decisions
tests/support). It uses ring's HMAC-SHA1 and theaescrate's block cipher, so the two sides share no crypto code. Both crates were already inCargo.lock; they are dev-dependencies only, and no new crate is added.Round 2: the recorded handshake
tests/capture.rsreplays two joins that wpa_supplicant v2.10 recorded on the T14 bench (Ubuntu 24.04). One process was the access point (mode=2) and the other the station, over twomac80211_hwsimradios. One join used plain PSK, the other required PMF.tests/fixtures/{psk,pmf}.pcap, about 1.3 KB each. Each holds six records of the radiotap capture, unmodified: the Probe Response, the Association Request and the four EAPOL-Key frames.NOTICEandCOMMITTED_FILEScarry their provenance and digests. The test takes the RSNEs from the Probe Response and the Association Request.-Klog printed. Both tests passed on the first run.issues/the-wlan-handshakes-readings-of-802-11-are-unchecked-against-its-text.md, 24 rows after review r1, owner the orchestrator.Round-2 checks, both exit 0:
cargo run -- --ci hostataf13f26ec: 107 steps green. The capture tests ran in it, and so did the licence and sourcegate gates over the new fixtures.cargo clippy -p toyos-wlan-handshake --all-targets.Evidence: https://github.com/ToyOSOrg/evidence/blob/807c77d17983f8bcaff037935664588246f99b8f/w2a-r2/README.md (the masked capture, the extraction tools, the control and the CI log).
Round 4: recorded PTK rekeys
tests/fixtures/rekey-{psk,pmf}.txtcome from a second capture in which the station'swpa_ptk_rekey=6drove two rekeys per run. The supplicant's six answers per run must be the station's, byte for byte. The TKs must match the station's log. The group keys are handed out only at the join, as the station itself did not reinstall them. Reinstalling the GTK, or reusing the SNonce in a rekey, reds both tests.--ci hostexit 0 (107 steps) at 26652fe. Evidence: https://github.com/ToyOSOrg/evidence/blob/2abd65ded9086596565f9f694220ea5435755310/w2a-r4/README.mdChecks (round 1)
All at
228e4f76aunless stated otherwise.cargo test -p toyos-wlan-handshake: 21 tests, exit 0.cargo test -p toyos-crypto --features wpa2 --test vectors: exit 0. It now includes Annex J's four PRF vectors and two more pass-phrase-to-PSK vectors, plus RFC 6070's one case a pass-phrase and SSID can be. Each expected value was also recomputed with Python'shashlib/hmacbefore it went in.cargo clippy -p toyos-wlan-handshake --all-targets: clean.cargo run -- --ci host: exit 0, 107 steps all green (run started at 31a5254; it picked up the RFC 6070 test, which ran in it; the rest of 228e4f7 is an issue record and one comment, after which the crate's tests and clippy and the crypto vectors were rerun at 228e4f7, exit 0).Negative controls. Six mutations, each applied as a checked patch, built, tested and reverted in the same run. Each reds exactly its target tests (exit 101):
message_3s_rsne_must_be_the_beaconsa_replayed_counter_is_refusedevery_single_octet_change_to_message_3_is_refuseda_resent_message_1_keeps_its_snoncegroup_key_id_0_is_refusedEvidence: https://github.com/ToyOSOrg/evidence/blob/74cfa5780d85f2cb5aab9750e1d4e29f8c1f2feb/w2a-r1/README.md
No guest or metal test applies: the crate is pure, and nothing in an image uses it until W4.
Round 3. Rebased onto main a2a4548, with review r1's fixes at 1679ef9.
--ci hostexit 0 (107 steps) at both b3979f2 and 1679ef9. Evidence: https://github.com/ToyOSOrg/evidence/blob/f534bf79cb3bc67609d9f47cbbb503dcd7fffbaa/w2a-r3/README.mdUnresolved
group_rekeyand never rekeyed, and itswpa_clihas noREKEY_GTK. Group message 1 is tested only against the tests' authenticator. The PTK rekey is recorded (round 4).issues/the-wlan-handshakes-readings-of-802-11-are-unchecked-against-its-text.md. The recording confirms the frames and keys, but not the citations or the refusals. The test-vector values are checked by computation; the Annex J subclause each one comes from is not cited.🤖 Generated with Claude Code