Skip to content

Fix the FreeRADIUS-verifiable audit findings: retransmits, one shared probe, honest latency and cert verdicts - #18

Merged
utarafdar merged 2 commits into
mainfrom
fix/freeradius-audit
Sep 15, 2026
Merged

utarafdar merged 2 commits into
mainfrom
fix/freeradius-audit

Conversation

@utarafdar

Copy link
Copy Markdown
Contributor

From a full static review of the probe against the FreeRADIUS 3.2.x source. Every item here is verified by unit tests and the FreeRADIUS Docker smoke test. The NPS-dependent questions are tracked separately and are not answered by this PR.

Transport

  • UDP retransmits (RFC 5080 §2.2.1). The same datagram is re-sent from the same socket at 40% and 70% of the timeout, never before 1.5 s so a FreeRADIUS reject_delay reply is not duplicated. A single lost packet is now a WARN "answered after N retransmit(s)" with a retransmits field, instead of "No reply — server down" plus the full registration hint. This was the most likely source of false pages in a scheduled --strict run.
  • Stray datagrams carrying a foreign Identifier are ignored rather than failing the exchange.

Fewer requests, fewer failed-auth log lines

  • reachability, shared-secret and blastradius-posture now ride on one PAP-shaped Access-Request (RFC 2865 §4.1 compliant, unlike the old credential-less reachability packet). One rejected login in the server's log per run instead of three; 50 instead of 150 under --count 50.
  • When that request gets no reply, the auth, certificate and MTU checks skip immediately (still marked as timeouts for --count) instead of each waiting out the full timeout.

Honest readings

  • Latency. Reachability reports the Status-Server round trip when the server answers one (rtt_source=status-server); otherwise it states that the figure includes FreeRADIUS's reject_delay, which is 1 s by default. Previously every LAN server "answered in ~1000ms".
  • Certificate chain. A leaf issued straight from a private root (single-tier AD CS, FreeRADIUS's own CA) is no longer reported as an incomplete chain — there is no intermediate to send. The WARN now fires only when the missing issuer is demonstrably an intermediate (an AIA CA-Issuers URL, or an intermediate-style name). CA certificates the server does send are checked for expiry, which was never done before.
  • CN-only certificates. A match by Common Name with no SAN is a WARN, not a FAIL: Windows supplicants accept it, Android 11+ and iOS do not.
  • PEAP. An outer Access-Reject / EAP-Failure after the MSCHAPv2 response is reported as a credential reject, not "the exchange did not complete, the server may not offer PEAP-MSCHAPv2".
  • EAP-TLS. A handshake aborted before the server ever requested a client certificate is no longer blamed on the certificate; it names the real candidates (no common TLS version or cipher, or an up-front policy reject).

Safety

  • --count stops after the first iteration in which the server rejected the test credentials, and says so (repeat.stopped_early in --json). Repeating a rejected password up to 50 times is how a test account trips the domain lockout threshold, after which the aggregate would report a self-inflicted lockout as a "consistent configuration failure".

Protocol

  • The PEAP Result-TLV acknowledgement carries only a Result TLV and no longer echoes the server's TLV payload. Windows NPS sends a Crypto-Binding TLV alongside the Result TLV; echoing the server's own binding back is an invalid response, and NPS reason code 301 is "cryptobinding TLV that is not valid ... a MITM attack is in progress". FreeRADIUS only inspects one byte of the ack, which is why this was never visible in the smoke test. NPS behaviour still has to be verified in a lab.
  • The failure-path TLV acknowledgement is now shipped, so the server closes the session instead of waiting for it.
  • TTLS inner PAP pads the password to a multiple of 16 octets (RFC 5281 §11.2.5), as wpa_supplicant does.

Flags and input

  • --called-station-id and --calling-station-id. Target already had the fields but nothing set them, so the probe could land on a different policy than real clients (NPS conditions and FreeRADIUS unlang routinely match the SSID or MAC).
  • Bare and bracketed IPv6 literals in --server.
  • A note when --timeout is under 2 s, since reject_delay can then look like packet loss.

Also

  • No more empty Vendor-311 line on EAP accepts (the MPPE key attributes are skipped, and the placeholder they left behind is gone).
  • The smoke test regenerates the FreeRADIUS image's test certificates at container start. They are valid for 60 days from the image build and had expired on 2026-08-02, so EAP-TLS and the certificate check were failing for a reason unrelated to the probe.

Verification

go vet ./..., go test ./... and ./test/freeradius-smoke.sh all pass. New tests cover the retransmit path and schedule, the Result-TLV-only acknowledgement, the shared base exchange (asserting the server sees exactly one request for three checks), the Status-Server latency source, the repeat lockout guard, and four certificate cases (private-root leaf, missing intermediate, CN-only, expired intermediate).

🤖 Generated with Claude Code

utarafdar and others added 2 commits September 15, 2026 13:21
… probe, honest latency and cert verdicts

From the 2026-09-15 static audit (FreeTools/probe-audit-2026-09-15.md). Every
item here is verified by unit tests and the FreeRADIUS Docker smoke test; the
NPS-dependent questions are tracked separately.

Transport
- UDP retransmits (RFC 5080 §2.2.1): the same datagram is re-sent from the same
  socket at 40%/70% of the timeout, never before 1.5 s so a FreeRADIUS
  reject_delay reply is not duplicated. A single lost packet is now a WARN
  "answered after N retransmit(s)" with a retransmits field, not "server down".
- Stray datagrams with a foreign Identifier are ignored instead of failing the
  exchange.

Fewer requests, fewer failed-auth log lines
- reachability, shared-secret and blastradius-posture ride on ONE PAP-shaped
  request (RFC 2865 §4.1 compliant, so NPS should answer it too): one rejected
  login in the server log per run instead of three.
- When that request gets no reply, the auth, certificate and MTU checks skip at
  once (marked as timeouts for --count) instead of each waiting out the timeout.

Honest readings
- Reachability latency comes from the Status-Server reply when available
  (rtt_source=status-server); otherwise the output says the figure includes
  FreeRADIUS's reject_delay.
- Certificate chain: a leaf issued straight from a private root is no longer an
  "incomplete chain" WARN; the WARN fires only when the missing issuer is
  demonstrably an intermediate (AIA URL or intermediate-style name). CA
  certificates sent with the leaf are checked for expiry. A Common-Name-only
  match (no SAN) is a WARN, not a FAIL: Windows accepts it, modern clients don't.
- PEAP: an outer Access-Reject/EAP-Failure after the MSCHAPv2 response is
  reported as a credential reject, not "server may not offer PEAP".
- EAP-TLS: a handshake aborted before the server asked for a client certificate
  is no longer blamed on the certificate.

Safety
- --count stops after the first iteration in which the server rejected the
  test credentials (repeat.stopped_early in --json): repeating a rejected
  password is how test accounts get locked out.

Protocol
- PEAP Result-TLV acknowledgement carries only a Result TLV; it no longer echoes
  the server's TLV payload (NPS sends a Crypto-Binding TLV there; echoing it
  back is invalid). NPS behaviour still to be verified in a lab.
- The failure-path TLV ack is shipped so the server closes the session.
- TTLS inner PAP pads the password to 16 octets (RFC 5281 §11.2.5).

Flags and input
- --called-station-id / --calling-station-id (wireless and MAC policies).
- Bare and bracketed IPv6 literals in --server.
- A note when --timeout is under 2 s (reject_delay can look like loss).

Also: no more empty "Vendor-311" line on EAP accepts (MPPE keys skipped), and
the smoke test regenerates the FreeRADIUS image's 60-day test certificates at
container start (they had expired, breaking EAP-TLS regardless of the probe).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
….5.0 summary

The policy section only named NAS-Port-Type as a branching input; SSID
(Called-Station-Id) and client MAC (Calling-Station-Id) are just as common and
now have flags. Adds a short "What's new" block so the behaviour changes
(retransmits, one shared request, latency source, certificate verdicts) are
visible to someone landing on the repo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@utarafdar
utarafdar merged commit 220f993 into main Sep 15, 2026
3 checks passed
@utarafdar
utarafdar deleted the fix/freeradius-audit branch September 15, 2026 07:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant