Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Fill in each section below. Delete any that are not applicable.

## Related requirement / task

<!-- e.g. FR-PARSE-3. Link the ARCHITECTURE.md section the change relates to. -->
<!-- e.g. FR-80211-3. Link the ARCHITECTURE.md section the change relates to. -->

## Changes

Expand All @@ -25,8 +25,8 @@ Fill in each section below. Delete any that are not applicable.
## Dependency changes

<!-- If this PR adds a crate dependency, justify it against the rejected-crate
policy in CONTRIBUTING.md (`flate2` and `clap` are the entire runtime budget).
Delete this section otherwise. -->
policy in CONTRIBUTING.md (`flate2`, `crc32fast`, `clap`, `rayon`, and `sysinfo`
are the entire runtime budget). Delete this section otherwise. -->

## Notes for reviewer

Expand Down
14 changes: 7 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
# cargo test -- compiles and runs unit + integration tests.
# cargo doc -- builds HTML API documentation from doc-comments.
# clippy -- the Rust linter. Part of the standard toolchain.
# MSRV -- Minimum Supported Rust Version. wpawolf declares 1.85.
# MSRV -- Minimum Supported Rust Version. wpawolf declares 1.95.
# =============================================================================

name: CI
Expand Down Expand Up @@ -226,11 +226,11 @@ jobs:

# ---------------------------------------------------------------------------
# msrv -- Minimum Supported Rust Version. Cargo.toml declares
# `rust-version = "1.85"`. This job proves we haven't accidentally used a
# `rust-version = "1.95"`. This job proves we haven't accidentally used a
# language feature newer than that.
# ---------------------------------------------------------------------------
msrv:
name: msrv (1.85)
name: msrv (1.95)
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
Expand All @@ -239,14 +239,14 @@ jobs:
with:
persist-credentials: false

- name: Install Rust 1.85 toolchain
# rust-toolchain.toml pins a different version for primary jobs; here
- name: Install Rust 1.95 toolchain
# rust-toolchain.toml uses stable (latest) for primary jobs; here
# we install a second toolchain side-by-side and make it the default
# for this job only. --profile minimal skips extras.
run: |
set -euo pipefail
rustup toolchain install 1.85.0 --profile minimal --component clippy
rustup default 1.85.0
rustup toolchain install 1.95.0 --profile minimal --component clippy
rustup default 1.95.0

- name: Cache cargo build artefacts
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
Expand Down
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ docs/

# Top-level tools/ holds local-only scratch space. The wpawolf-fixturegen
# crate (a workspace member, see tools/fixturegen/Cargo.toml) is committed.
# tools/audit_citations.sh is the developer-side parity-citation audit
# (skips cleanly when ref/ is missing) and is also committed.
# tools/audit_citations.sh (parity-citation audit; skips cleanly when ref/ is
# missing) and tools/audit_stats.sh (STATS.md vs stats.rs drift gate) are
# also committed.
tools/*
!tools/fixturegen/
!tools/audit_citations.sh
!tools/audit_stats.sh

# =============================================================================
# .gitignore -- Rust/Cargo / VS Code / Linux / macOS / Windows
Expand Down
223 changes: 51 additions & 172 deletions ARCHITECTURE.md

Large diffs are not rendered by default.

71 changes: 42 additions & 29 deletions CHANGELOG.md

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Thanks for wanting to contribute. `wpawolf` is a narrow-scope tool (WPA/WPA2/WPA

```
wpawolf/
├── src/ Rust source (input/, link/, ieee80211/, store/, pair/, output/)
├── src/ Rust source (input/, link/, ieee80211/, extract/, store/, pair/, output/)
├── tests/ Integration tests + binary fixtures (incl. tests/fixtures/generated/ corpus)
├── tools/fixturegen/ Workspace crate that emits the test-capture corpus (separate Cargo crate)
├── .github/ CI / Security / Release workflows + issue + PR templates
Expand All @@ -26,7 +26,7 @@ wpawolf/
└── Makefile Developer workflow + cross-platform release builds
```

The project runs strict clippy (pedantic + nursery + cargo) with zero warnings, and the test suite covers 904 cases across lib + binary + integration. An external multi-GB regression dataset (out-of-tree) is exercised opportunistically before each release on real-world traffic that is too noisy or legally encumbered to commit.
The project runs strict clippy (pedantic + nursery + cargo) with zero warnings, and the test suite covers 920 cases across lib + binary + integration. An external multi-GB regression dataset (out-of-tree) is exercised opportunistically before each release on real-world traffic that is too noisy or legally encumbered to commit.

## Before you open a PR

Expand All @@ -35,7 +35,7 @@ make check-all
make check-parity # only when touching pairing / output / extraction
```

`make check-all` runs, in order: `fmt`, `clippy` (zero warnings), `cargo deny`, `cargo check`, `cargo test`, `cargo doc` with warnings-as-errors, ASCII hygiene, LF hygiene, and unused-dependency detection. A green `check-all` is required for review.
`make check-all` runs, in order: `fmt`, `clippy` (zero warnings), `cargo deny`, the `audit-citations` hcxpcapngtool line-citation check, the `audit-stats` banner-contract check ([`STATS.md`](STATS.md) vs `src/stats.rs`, both directions), `cargo check`, `cargo test`, `cargo doc` with warnings-as-errors, ASCII hygiene, LF hygiene, and unused-dependency detection. A green `check-all` is required for review.

`make check-parity` re-runs the superset test against `hcxpcapngtool` with `CI=true` set, which converts a missing or stale oracle from a soft skip into a hard failure. Run this whenever you change anything in `src/pair/`, `src/output/`, `src/store/`, or `src/extract/`.

Expand Down Expand Up @@ -68,15 +68,15 @@ If hcxpcapngtool is missing or older than 7.0.1, the test prints a clearly-tagge

## Dependency additions

Require a paragraph-long justification in the PR body addressing the rejected-crate policy in [`ARCHITECTURE.md §4`](ARCHITECTURE.md). Bar is high: target runtime dep count is 4 (`flate2`, `clap`, `rayon`, `sysinfo`). Dev-dependencies are less restrictive but still subject to `cargo deny` licence allow-list.
Require a paragraph-long justification in the PR body addressing the rejected-crate policy in [`ARCHITECTURE.md §4`](ARCHITECTURE.md). Bar is high: target runtime dep count is 5 (`flate2`, `crc32fast`, `clap`, `rayon`, `sysinfo`). Dev-dependencies are less restrictive but still subject to `cargo deny` licence allow-list.

## Adding a capture fixture

- Under 1 MiB, commit to `tests/fixtures/pcaps/`.
- Over 1 MiB, keep out-of-tree and reference it from benchmarks only.
- **Redact** real ESSIDs and client MAC addresses unless the capture comes from a lab network you control. wireshark's *Edit → Preferences → Name Resolution* + `editcap` can help.

The companion crate at [`tools/fixturegen/`](tools/fixturegen/) emits a deterministic 75-fixture pcap/pcapng corpus covering every (hash category × PMKID site × N#E# combo × link-layer × edge case) tuple, with cryptographically valid PMK / PMKID / MIC values — 117 of 123 lines crack end-to-end through hashcat 7.1.2 with PSK `hashcat!` (the 6 that don't are documented hashcat kernel limitations, see [`HASHCAT-CURRENT-FORMATS.md`](HASHCAT-CURRENT-FORMATS.md) §8.1).
The companion crate at [`tools/fixturegen/`](tools/fixturegen/) emits a deterministic 75-fixture pcap/pcapng corpus covering every (hash category × PMKID site × N#E# combo × link-layer × edge case) tuple, with cryptographically valid PMK / PMKID / MIC values — every legacy-sink line cracks end-to-end through hashcat 7.1.2 with PSK `hashcat!` except the documented kernel limitations (the PSK-SHA-256 PMKID lines and the two APLESS FT combos; see [`HASHCAT-CURRENT-FORMATS.md`](HASHCAT-CURRENT-FORMATS.md) §8).

## Reporting hashcat / hcxtools gaps

Expand Down
12 changes: 6 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions HASHCAT-CURRENT-FORMATS.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ Hashcat's mode-22000 PMKID parser does not consume this byte (it reads the `***`

## §7 How the 11 wpawolf hash types map onto the four legacy prefixes

`wpawolf` classifies every PSK-crackable hash into one of eleven types (see [`HASHCAT-NEW-FORMATS.md`](HASHCAT-NEW-FORMATS.md) for the full classification). When the legacy sinks (`--22000-out`, `--37100-out`) are configured, each row is rewritten with a legacy prefix; this table shows what comes out and how hashcat handles it.
`wpawolf` classifies every PSK-crackable hash into one of eleven types (see [`HASHCAT-NEW-FORMATS.md`](HASHCAT-NEW-FORMATS.md) for the full classification). When the legacy sinks (`--22000-out`, `--37100-out`) are configured, each SHA-1 / SHA-256-family row is rewritten with a legacy prefix; the SHA-384 family is suppressed from the legacy sinks entirely (`legacy_sink_for` returns no sink) because its 24 B MIC would only produce `Token length exception` parse errors at hashcat startup. This table shows what comes out and how hashcat handles it.

| 11-type row | Legacy prefix | Legacy sink | Hashcat reads via | Cracks today? |
|-------------------------|--------------------|-----------------|-------------------|--------------------|
Expand All @@ -243,12 +243,12 @@ Hashcat's mode-22000 PMKID parser does not consume this byte (it reads the `***`
| PSK-SHA256-EAPOL | `WPA*02*` | `--22000-out` | `keyver=3` | yes (m22000_aux3) |
| FT-PSK-PMKID | `WPA*03*` | `--37100-out` | direct prefix | yes (m37100 type=3)|
| FT-PSK-EAPOL | `WPA*04*` | `--37100-out` | `keyver=3` | partial (m37100 type=4) -- N2E3 / N4E3 APLESS combos do not crack, see §8.1 |
| PSK-SHA384-PMKID | `WPA*01*` | `--22000-out` | best-effort | **no** -- kernel runs HMAC-SHA1, line is HMAC-SHA384 |
| PSK-SHA384-EAPOL | `WPA*02*` | `--22000-out` | `keyver=0` | **no** -- 24 B MIC truncated to 16 B; module rejects keyver=0 |
| FT-PSK-SHA384-PMKID | `WPA*03*` | `--37100-out` | best-effort | **no** -- kernel runs SHA-256 FT chain, line is SHA-384 |
| FT-PSK-SHA384-EAPOL | `WPA*04*` | `--37100-out` | `keyver=0` | **no** -- 24 B MIC truncated; module rejects keyver=0 |
| PSK-SHA384-PMKID | -- (suppressed) | none -- `--psk-sha384-out` / `-o` only | n/a | **no** -- HMAC-SHA384 PMKID has no kernel; line never written to a legacy sink |
| PSK-SHA384-EAPOL | -- (suppressed) | none -- `--psk-sha384-out` / `-o` only | n/a | **no** -- 24 B MIC does not fit the 16 B field; loader would reject keyver=0 |
| FT-PSK-SHA384-PMKID | -- (suppressed) | none -- `--ft-psk-sha384-out` / `-o` only | n/a | **no** -- needs FT-KDF-SHA-384 chain; line never written to a legacy sink |
| FT-PSK-SHA384-EAPOL | -- (suppressed) | none -- `--ft-psk-sha384-out` / `-o` only | n/a | **no** -- SHA-384 24 B MIC + FT chain, both unsupported |

Six of eleven rows route cleanly through the legacy scheme today. One row misroutes silently inside the kernel (PSK-SHA256-PMKID -- the line is well-formed but the cracker checks the wrong primitive). Four rows have no usable legacy path at all -- the SHA-384 family's 24 B MIC does not fit the 16 B `<mic>` field, and the module rejects `keyver=0` even before reaching the kernel.
Six of eleven rows route cleanly through the legacy scheme today. One row misroutes silently inside the kernel (PSK-SHA256-PMKID -- the line is well-formed but the cracker checks the wrong primitive). Four rows have no usable legacy path at all -- the SHA-384 family's 24 B MIC does not fit the 16 B `<mic>` field and the module rejects `keyver=0` even before reaching the kernel, so wpawolf never writes them to the legacy sinks; they surface only on the per-AKM sinks (`--psk-sha384-out`, `--ft-psk-sha384-out`) and the combined `-o` under their `WPA*08*..*11*` prefixes.

---

Expand Down
10 changes: 5 additions & 5 deletions HASHCAT-PROPOSED-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Phase 1 is shippable without any hashcat-core patch. Phase 2 unlocks the maximum
1. **One module per input semantic.** Mode 22002 takes a passphrase and runs PBKDF2; mode 22003 takes a 64-hex PMK and skips PBKDF2. The on-disk hash format and the post-PMK math are identical between them. This mirrors the relationship between today's 22000 and 22001.
2. **Type-driven dispatch.** The 2-digit prefix code after `WPA*` is the SOLE routing axis. The loader reads the type, picks the kernel, sets the MIC width, and decides whether to expect FT extras. No `keyver` byte inspection, no AKM inference, no pair-of-fields correlation.
3. **PBKDF2 reuse across all 11 types per ESSID.** A hash file containing every PSK family the operator's capture produced runs PBKDF2 *once per (ESSID, work-item)* and dispatches per-type post-PMK math from the cached PMK in `tmps[].out`. PBKDF2 is the dominant cost (4096 SHA-1 iterations); the per-type math is ~0.1% of that on mode 22002.
4. **Single-pass cracking of mixed-type files.** The natural input is a `wpawolf -o` per-AKM file containing every hash extracted from one capture. One `hashcat -m 22002 all.taxo wordlist.txt` cracks every variant. No per-type hash-file splitting, no per-mode re-runs.
4. **Single-pass cracking of mixed-type files.** The natural input is a `wpawolf -o` per-AKM file containing every hash extracted from one capture. One `hashcat -m 22002 all.hash wordlist.txt` cracks every variant. No per-type hash-file splitting, no per-mode re-runs.
5. **Greenfield format consumption.** New format only. The new modules never see a legacy line. This eliminates entire categories of loader complexity (the `keyver` peek, the AKM-from-`WPA*01*` guessing problem, the HCCAPX binary path).
6. **Two-phase implementation.** Phase 1 is a self-contained ship target requiring no hashcat-core changes. Phase 2 is an independently reviewable hashcat-core patch plus a kernel-layout refactor. Operators see no CLI or hash-format change between phases.

Expand Down Expand Up @@ -199,8 +199,8 @@ The branch is a single `switch` at the top of the kernel body. Inside each case

Inside one wavefront, GPU lanes that take different `switch` arms run serialised. Realistic mix:

- Operator runs `wpawolf -o all.taxo` and feeds it to `hashcat -m 22002`.
- `all.taxo` carries one `WPA*<type>*` line per detected handshake; one capture often has many (PMKID + 3 EAPOL pair combos per session).
- Operator runs `wpawolf -o all.hash` and feeds it to `hashcat -m 22002`.
- `all.hash` carries one `WPA*<type>*` line per detected handshake; one capture often has many (PMKID + 3 EAPOL pair combos per session).
- Within a single salt (ESSID), every type the capture produced shares the same PBKDF2 output; the host buckets digests by salt before launching aux kernels.
- The host launches `m22002_aux2` once per (salt, work-item-batch) with all type-4 + type-5 digests for that salt visible. A wavefront iterating digest_pos sees mixed type 4 / type 5 lanes -> ~2x divergence cost on the post-PMK math (cheap relative to PBKDF2 on mode 22002, but visible on mode 22003).

Expand Down Expand Up @@ -375,8 +375,8 @@ The migration from Phase 1 to Phase 2 is invisible to operators: same mode numbe
A test fixture covers all eleven types. For each:

```
hashcat -m 22002 fixtures/typeNN.taxo wordlist.txt
hashcat -m 22003 fixtures/typeNN.taxo pmk_list.txt
hashcat -m 22002 fixtures/typeNN.hash wordlist.txt
hashcat -m 22003 fixtures/typeNN.hash pmk_list.txt
# expected: cracks the fixture password / matches the fixture PMK
```

Expand Down
15 changes: 13 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.PHONY: fmt fmt-fix lint lint-fix doc dev build check \
test test-release test-matrix check-parity \
audit audit-citations machete \
audit audit-citations audit-stats machete \
ascii-check lf-check hygiene \
build-linux-musl build-linux-arm-musl \
build-macos-arm build-macos-x86 build-macos-universal \
Expand Down Expand Up @@ -80,6 +80,10 @@ dev:
build:
$(CARGO) build --profile release --all-features

# Alias so `make release` builds the native release binary.
.PHONY: release
release: build

# Fast type-check, no codegen.
check:
$(CARGO) check --all-targets --all-features
Expand Down Expand Up @@ -118,6 +122,13 @@ audit:
audit-citations:
./tools/audit_citations.sh

# Verify ARCHITECTURE.md section 9 (the stats banner contract) against the
# actual counter fields in src/stats.rs and src/store/fragments.rs, both
# directions. Catches drift when a counter is added, renamed, or removed
# without updating the catalogue -- the doc-rot class found in the 2026-06 audit.
audit-stats:
./tools/audit_stats.sh

machete:
$(CARGO) machete

Expand Down Expand Up @@ -341,4 +352,4 @@ clean:
# -- Gates ---------------------------------------------------------------------

# Full verification gate -- run before every push.
check-all: fmt lint audit audit-citations check test-matrix doc hygiene machete
check-all: fmt lint audit audit-citations audit-stats check test-matrix doc hygiene machete
Loading
Loading