Windows Server for beginners. Part 27: Setting up Wi-Fi with 802.1X

All parts of the series “Windows Server for beginners”

Radio, 802.11 frames, association and the WPA2 four-way handshake are covered in part 3 of the networking series. That part also used a shared network password — a PSK from which every client derives its keys. In an office a shared password doesn't work: every former employee knows it, it can't be revoked for one person, and it says nothing about who connected. The replacement is 802.1X: joining the network requires authentication against the directory, every client gets its own keys, and access is revoked by disabling the account. The RADIUS server from the previous part is ready; now come the access point, the client and the policies.


Personal and Enterprise

WPA2 and WPA3 have two modes each, and the difference between them is where the PMK comes from — the master session key the handshake starts with:

Personal Enterprise
Where the PMK comes from from the shared password (WPA2: PBKDF2 of the PSK and SSID; WPA3: the SAE exchange) from EAP: the RADIUS server sends it to the AP in MS-MPPE-Recv-Key after a successful authentication
Who can connect anyone who knows the password a directory account or a certificate holder
Revocation change the password for everyone disable the account
Keys all derived from one secret each session has its own, nobody knows anyone else's
What the AP knows nothing about the user the name, the group, the VLAN from the RADIUS response

PBKDF2 is a function that stretches a password into a key by hashing it many times; with WPA2-Personal anyone who has recorded a handshake can try passwords offline. SAE (Simultaneous Authentication of Equals) in WPA3-Personal is an exchange in which both sides prove they know the password without sending anything that could be used to guess it without talking to the AP. Enterprise has no network password at all: the PMK is the result of authenticating one particular client.

WPA3-Enterprise is the same 802.1X with two additions: mandatory management frame protection (PMF, 802.11w: disconnect frames are signed, and a forged deauth can't knock a client off) and an optional 192-bit mode with a fixed set of strong algorithms, in which EAP-TLS is the only EAP method allowed. Windows has both modes since Windows 10 2004 and Windows Server 2022, and in the profile WPA3-Enterprise became a separate value (WPA3ENT) in Windows 11. WPA2/WPA3 transition mode on one SSID lets older clients connect with WPA2-Enterprise and newer ones with WPA3. A domain of modern Windows machines and phones fits WPA3-Enterprise; a fleet with printers and older devices fits transition mode.


Three roles

 Supplicant               Authenticator                 Authentication server
 (client, ws1)            (access point)                (nps1, RADIUS)
    │                         │                               │
    │ ─ 802.11 association ──►│  port closed: passes          │
    │                         │  only EAPOL                   │
    │ ─ EAPOL-Start ─────────►│                               │
    │◄─ EAP-Request/Identity ─┤                               │
    │ ─ EAP-Response/Identity►│ ─ Access-Request ────────────►│
    │◄─ EAP-Request (PEAP) ───┤◄─ Access-Challenge ───────────┤
    │ ─ EAP-Response ────────►│ ─ Access-Request ────────────►│   TLS, MSCHAPv2 inside
    │      … a dozen more …   │        … relaying …           │   or a certificate
    │◄─ EAP-Success ──────────┤◄─ Access-Accept + PMK + VLAN ─┤
    │                         │  port open for this MAC       │
    │◄─ EAPOL-Key 1/4 ────────┤  four-way handshake           │
    │ ─ EAPOL-Key 2/4 ───────►│  from the PMK: PTK and GTK    │
    │◄─ EAPOL-Key 3/4 ────────┤                               │
    │ ─ EAPOL-Key 4/4 ───────►│                               │
    │ ─ DHCP Discover ───────►│  normal traffic               │

The supplicant is the client: in Windows it's the WLAN AutoConfig service (wlansvc) for wireless networks and Wired AutoConfig (dot3svc) for wired ones. The authenticator is the access point or switch: it keeps the port closed, passing only EAPOL frames (EAP over LAN, EtherType 0x888E), repackages EAP between the client and RADIUS, and opens the port on Access-Accept. The authentication server is NPS. The AP doesn't understand the EAP content, never sees the password and makes no decisions: it carries out the server's answer, including the VLAN number if there is one. For domain passwords EAP carries PEAP: a TLS tunnel in which the server presents a certificate, with EAP-MSCHAPv2 inside — a challenge-response on the NT hash of the password; for certificates it carries EAP-TLS, where both sides present certificates (both methods are covered in the previous part).


The access point

On the AP (or the AP controller) there are four settings, the same for every vendor: SSID CorpWiFi, security WPA2/WPA3-Enterprise, RADIUS server 192.168.1.30:1812 with the same secret as the RADIUS client in NPS (the wifi template in the previous part), and a second server if there are two NPS servers. Accounting on 1813 is turned on if you need it: without it NPS sees only authentication, and there will be no Accounting-Request in the accounting log, as with the APs in the previous part. The fifth setting, if you want per-user VLANs, is "dynamic VLAN assignment via RADIUS" (each vendor names it differently). The AP then tags the client's traffic with the 802.1Q tag of the VLAN that came in the Access-Accept as Tunnel-Type = VLAN (13), Tunnel-Medium-Type = IEEE-802 (6), Tunnel-Private-Group-ID = 20, and the AP's switch port is a trunk with the VLANs it needs. Usually the same place lets you choose what to do with a client for whom the server sent no VLAN: leave it in the SSID's network or reject it.

In every request the AP sends a Called-Station-Id like AA-BB-CC-1E-00-01:CorpWiFi — the BSSID of the radio the client connected through, and the SSID (the format comes from RFC 3580). A dual-band AP has two BSSIDs, and in the NPS log one AP shows up under two values. By the SSID in this attribute NPS can tell requests from CorpWiFi apart from requests from the guest CorpGuest on the same AP, and this condition belongs in every policy.


The profile on the client

A wireless network profile is XML, and everything that matters in it is in the OneX → EAPConfig block. You can build it on one machine in the UI and export it (netsh wlan export profile name="CorpWiFi" folder=C:\Temp), or write it following the documentation sample:

<?xml version="1.0"?>
<WLANProfile xmlns="http://www.microsoft.com/networking/WLAN/profile/v1">
  <name>CorpWiFi</name>
  <SSIDConfig><SSID><name>CorpWiFi</name></SSID></SSIDConfig>
  <connectionType>ESS</connectionType>
  <connectionMode>auto</connectionMode>
  <MSM>
    <security>
      <authEncryption>
        <authentication>WPA3ENT</authentication>
        <encryption>AES</encryption>
        <useOneX>true</useOneX>
      </authEncryption>
      <PMKCacheMode>enabled</PMKCacheMode>
      <OneX xmlns="http://www.microsoft.com/networking/OneX/v1">
        <authMode>machineOrUser</authMode>
        <EAPConfig>
          <EapHostConfig xmlns="http://www.microsoft.com/provisioning/EapHostConfig">
            <EapMethod>
              <Type xmlns="http://www.microsoft.com/provisioning/EapCommon">25</Type>
              <AuthorId xmlns="http://www.microsoft.com/provisioning/EapCommon">0</AuthorId>
            </EapMethod>
            <Config xmlns="http://www.microsoft.com/provisioning/EapHostConfig">
              <Eap xmlns="http://www.microsoft.com/provisioning/BaseEapConnectionPropertiesV1">
                <Type>25</Type>
                <EapType xmlns="http://www.microsoft.com/provisioning/MsPeapConnectionPropertiesV1">
                  <ServerValidation>
                    <DisableUserPromptForServerValidation>true</DisableUserPromptForServerValidation>
                    <ServerNames>nps1.corp.example.com</ServerNames>
                    <TrustedRootCA>3f 1a 2b 3c 4d 5e 6f 70 81 92 a3 b4 c5 d6 e7 f8 09 1a 2b 3c</TrustedRootCA>
                  </ServerValidation>
                  <FastReconnect>true</FastReconnect>
                  <InnerEapOptional>false</InnerEapOptional>
                  <Eap xmlns="http://www.microsoft.com/provisioning/BaseEapConnectionPropertiesV1">
                    <Type>26</Type>
                    <EapType xmlns="http://www.microsoft.com/provisioning/MsChapV2ConnectionPropertiesV1">
                      <UseWinLogonCredentials>true</UseWinLogonCredentials>
                    </EapType>
                  </Eap>
                  <EnableQuarantineChecks>false</EnableQuarantineChecks>
                  <RequireCryptoBinding>true</RequireCryptoBinding>
                </EapType>
              </Eap>
            </Config>
          </EapHostConfig>
        </EAPConfig>
      </OneX>
    </security>
  </MSM>
</WLANProfile>

What decides security and behaviour here:

  • authMode = machineOrUser — the computer authenticates with its own account (host/ws1.corp.example.com, in the directory CORP\WS1$) while nobody is signed in, the user with theirs after sign-in; the network is there before sign-in, and Group Policy, scripts and password changes work as on a wired connection. This is the default: without authMode in the profile Windows behaves the same way. Hence a consequence you'll see in "Hands-on": without an NPS policy for computers, every attempt before sign-in is rejected.
  • Type 25 is PEAP; inside it Type 26 is EAP-MSCHAPv2 with the Windows sign-in credentials (UseWinLogonCredentials), so nobody types a password.
  • ServerValidation: the thumbprint of the root certificate from part 25 (bytes separated by spaces; a placeholder here) and the server name. The name is compared with the subject of the server certificate exactly, or as a regular expression if the string contains at least one * (nps.*\.corp\.example\.com); several names are separated by ;. Without ServerNames the client accepts a certificate with any name from any trusted root, without TrustedRootCA — from any root in its store. DisableUserPromptForServerValidation = true means don't ask the user "trust this?", just fail.
  • RequireCryptoBinding — require the server to send the cryptobinding TLV, with which PEAP binds the inner authentication to the outer TLS tunnel: an MSCHAPv2 response captured in one tunnel can't be relayed into another.
  • FastReconnect — PEAP fast reconnect: the client resumes the previous TLS session instead of a full handshake, and the server doesn't ask for the password again. PMKCacheMode — the PMK cache on the client: when it comes back to an AP it has already authenticated with, the handshake starts from the cached PMK and RADIUS isn't involved at all.

Installing the profile on a machine and checking it:

PS C:\> netsh wlan add profile filename=C:\Temp\CorpWiFi.xml user=all
PS C:\> netsh wlan show profiles

user=all makes the profile available to all users of the machine (it's also the default), and without interface= it goes to every wireless adapter. The wlansvc service has to be running: on a machine without a wireless adapter it's stopped, and every netsh wlan command answers with one line:

PS C:\> netsh wlan show profiles
The Wireless AutoConfig Service (wlansvc) is not running.

Centrally — with Group Policy: Computer Configuration → Policies → Windows Settings → Security Settings → Wireless Network (IEEE 802.11) Policies, where you create a policy with the same profile, the same PEAP tabs and the same certificate validation. It is stored in the directory inside the Group Policy object's container (CN=IEEE80211,CN=Windows,CN=Microsoft,CN=Machine,…) as an object of class ms-net-ieee-80211-GroupPolicy, with the whole XML in the ms-net-ieee-80211-GP-PolicyData attribute; wired policies use the class ms-net-ieee-8023-GroupPolicy, and the msieee80211-Policy class in the schema is left over from the binary policy format of Windows XP. The wireless policy extension applies it; there are no cmdlets for it, only the console. The second central route is to put the XML in SYSVOL and run netsh wlan add profile from a startup script or a scheduled task in Preferences; it falls short of the first in that it doesn't remove the profile when the policy is withdrawn. For a server certificate issued by a public CA (see below), TrustedRootCA is the thumbprint of the public root, which every machine already has.


NPS policies

Two network policies, both with conditions on the port type and the SSID: one for computers, one for users. In the console NPS → Network Policies → New; in a configuration export, two blocks. What's in them:

Wi-Fi — Domain Computers Wi-Fi — Domain Users
Conditions Windows Groups: CORP\Domain Computers; NAS Port Type: Wireless — IEEE 802.11; Called Station ID: .*:CorpWiFi$ Windows Groups: CORP\Domain Users; the rest the same
Permission Grant Grant
Constraints: methods EAP: Microsoft: Protected EAP (PEAP) with inner EAP-MSCHAP v2; server certificate nps1.corp.example.com; all other methods cleared the same
Settings: attributes Tunnel-Type = VLAN, Tunnel-Medium-Type = 802, Tunnel-Pvt-Group-ID = 20; Session-Timeout = 28800 VLAN 20 or one per group
Settings: account "Ignore user account dial-in properties" on on

Session-Timeout in a response to 802.1X is the time after which the AP re-authenticates the client (if Termination-Action = RADIUS-Request comes with it) or disconnects it (RFC 3580). Policy order: computers first, then users. Per-group VLANs are a third and fourth policy higher in the list, with a narrower group in the condition and a different Tunnel-Pvt-Group-ID: engineers in VLAN 30, everyone else in 20. For that the APs need trunks, and DHCP (part 8) needs a scope for every VLAN with relaying.

The NPS wizard for wireless connections, as in the previous part, creates a single policy, "Secure Wireless Connections" — for the groups you give it (Domain Users there), with no SSID condition and no VLAN. Computers don't match it, and their requests fall through to the default deny policy.

For EAP-TLS, where clients have certificates from the Corp Computer template of part 25, the "methods" constraint is Smart Card or other certificate, and there's no password in the exchange at all: this is the preferred option for domain computers, while PEAP stays for users without certificates and for non-domain devices.


Non-domain devices

Phones, contractors' laptops, personal devices. Three approaches, from simple to proper.

A separate SSID with a password — CorpGuest with WPA3-Personal, in a guest VLAN with no access inside. It doesn't solve access to resources, but it settles the question for half the devices.

PEAP with a domain name and password. It works everywhere, but the client must validate the server certificate, and for that it needs our root certificate. Android, since the Android 11 updates, doesn't let you choose "do not validate" and requires the CA and the server domain; iOS asks "trust?" on the first connection, and the user will tap "yes" to anything. The root is distributed through MDM (mobile device management) or a configuration profile, or — often better — NPS gets a certificate from a public CA for a name in your public domain, such as wifi.example.com: devices trust it out of the box, and the profile only needs that name. The name doesn't have to resolve in DNS: the client never connects to it, it only compares it with the certificate. The account in PEAP is alice@corp.example.com or CORP\alice; MSCHAPv2 means this is not for members of Protected Users.

EAP-TLS with a user certificate — MDM (Intune and the like) issues a certificate from a template via SCEP or a connector to the CA, the Wi-Fi profile arrives along with it, and no password is typed or stored on the device. SCEP (Simple Certificate Enrollment Protocol, RFC 8894) is a protocol in which the device generates its own key and sends a certificate request over HTTP to the CA's intermediary, presenting a one-time password from the MDM. For employees' phones this is the end state.


Wired 802.1X

A switch with dot1x on its ports is the same mechanism without the radio: the port is closed until EAP-Success, the authenticator is the switch, and the VLAN is assigned by the RADIUS response. On the Windows side the differences are: the Wired AutoConfig service (dot3svc) is stopped by default and starts manually, and without it the client doesn't answer EAP-Request; the policy is Wired Network (IEEE 802.3) Policies in the same Group Policy branch, with the same PEAP and certificate validation; the profile is netsh lan add profile filename=… (without interface= it becomes the machine profile for all wired adapters).

PS C:\> Get-Service dot3svc, wlansvc | Select-Object Name, Status, StartType

Name     Status StartType
----     ------ ---------
dot3svc Stopped    Manual
wlansvc Stopped    Manual

PS C:\> netsh lan show interfaces

The Wired AutoConfig Service (dot3svc) is not running.

That's a workstation where 802.1X hasn't been turned on yet (and there's no wireless adapter). To turn it on — Set-Service dot3svc -StartupType Automatic and Start-Service dot3svc, or the same service setting through Group Policy; after that netsh lan show interfaces shows the authentication state on each adapter.

Devices without a supplicant — printers, IP phones, cameras — are let in with MAB (MAC Authentication Bypass): the switch, having waited in vain for EAPOL, sends an Access-Request with the client's MAC address in User-Name and Calling-Station-Id, and often in User-Password with PAP (the password in clear text, hidden only by the RADIUS secret). NPS has two ways to handle it: accounts named after the MAC addresses in a separate group, plus a network policy with PAP allowed and the printer VLAN; or a connection request policy with a Calling-Station-Id condition and the "Accept users without validating credentials" setting — then NPS answers Accept without checking anything. This isn't authentication (a MAC can be spoofed) but inventory with isolation, and it should be treated as such: such a connection request policy must catch only the device ports and only their MACs.


What breaks

The most common symptoms and where to look:

  • Everyone dropped at once — the NPS certificate has expired. The client drops the conversation itself after receiving the server certificate, and NPS has neither a rejection nor a 6273 — the accounting log just has an Access-Challenge that nothing followed. That's why the NPS certificate's expiry is watched separately, and after renewal the new one is selected in every policy (the thumbprint in EAP-Configuration changes). For EAP-TLS the same happens when the CRL of the CA that issued the client certificates is unavailable (part 25): 6273 with code 258 or 259.
  • One client doesn't trust the server — the wrong root in TrustedRootCA, the certificate name doesn't match ServerNames, or after renewal NPS presents a different certificate. In NPS this isn't a rejection either but an exchange that broke off after the Access-Challenge with the certificate; the cause is on the client, in event 12013.
  • Code 22 — the EAP type in the profile doesn't match the policy: PEAP versus EAP-TLS, or the inner method.
  • Code 16 for a computer — the computer account password is out of sync (a snapshot rollback, part 18); for a user — a wrong or stale saved password.
  • Code 48 — no network policy matched: the wrong group, the wrong SSID in the condition, the wrong port type. If the default deny policy "Connections to other access servers" is last in the list, the same case gives code 65.
  • Connects, but no network — the VLAN: the AP isn't set up for dynamic VLANs, or the trunk doesn't carry that VLAN, or it has no DHCP.
  • Connects only after sign-in — there's no machine authentication: authMode is user, or NPS has no policy for Domain Computers and the computer's requests get 65. Computer policies aren't applied at startup.

On the client all of this shows up in the Microsoft-Windows-WLAN-AutoConfig/Operational log; the event codes and texts come from the provider manifest:

PS C:\> (Get-WinEvent -ListProvider Microsoft-Windows-WLAN-AutoConfig).Events |
    Where-Object Id -in 8001, 8002, 11005, 11006, 12011, 12012, 12013 |
    Sort-Object Id -Unique | ForEach-Object { '{0,5}  {1}' -f $_.Id, ($_.Description -split "`r?`n")[0] }
 8001  WLAN AutoConfig service has successfully connected to a wireless network.
 8002  WLAN AutoConfig service failed to connect to a wireless network.
11005  Wireless security succeeded.
11006  Wireless security failed.
12011  Wireless 802.1x authentication started.
12012  Wireless 802.1x authentication succeeded.
12013  Wireless 802.1x authentication failed.

The computer and the user differ not by event number but by the Identity field in 12012: host/ws1.corp.example.com or a user name. 12013 has the reason and the EAP error code, 8001 the authentication type and cipher of the connection, 8002 the reason for the failure. Wired 802.1X has its own log, Microsoft-Windows-Wired-AutoConfig/Operational, with events 15503 (authentication started), 15505 (succeeded) and 15514 (failed). netsh wlan show wlanreport, run as administrator, collects the events of the last three days into an HTML report grouped by session. On the server — 6272 and 6273 from the previous part, and the accounting log.


Hands-on

The output was taken on a server where NPS serves three access points with the single "Secure Wireless Connections" policy from the previous part; the AP names, SSID and addresses are replaced with the article's names.

Which certificate the server presents — by the thumbprint from the policy's EAP-Configuration:

PS C:\> Invoke-Command -ComputerName nps1 -ScriptBlock {
    $eap = (netsh nps show np | Select-String 'EAP-Configuration' | Select-Object -First 1).Line
    $thumb = [regex]::Match($eap, '14000000([0-9A-F]{40})').Groups[1].Value
    $cert = Get-Item "Cert:\LocalMachine\My\$thumb"
    $chain = [System.Security.Cryptography.X509Certificates.X509Chain]::new()
    $null = $chain.Build($cert)
    [PSCustomObject]@{
        Subject  = $cert.Subject
        DnsNames = $cert.DnsNameList.Unicode -join ', '
        EKU      = $cert.EnhancedKeyUsageList.FriendlyName -join ', '
        Chain    = ($chain.ChainElements | ForEach-Object { ($_.Certificate.Subject -split ', ')[0] }) -join ' → '
        Root     = $chain.ChainElements[$chain.ChainElements.Count - 1].Certificate.Thumbprint
    }
} | Format-List Subject, DnsNames, EKU, Chain, Root

Subject  : CN=wifi.example.com
DnsNames : wifi.example.com
EKU      : Server Authentication
Chain    : CN=wifi.example.com → CN=RapidSSL TLS RSA CA G1 → CN=DigiCert Global Root G2
Root     : DF3C24F9BFD666761B268073FE06D1CC8D4F82A4

This is the public CA option: the client profiles have ServerNames = wifi.example.com and TrustedRootCA with the thumbprint of DigiCert Global Root G2 (df 3c 24 f9 …), which is in the store of every Windows machine. The name isn't in DNS — Resolve-DnsName wifi.example.com answers DNS name does not exist. — and for Wi-Fi that doesn't matter: the client only compares the name.

Which APs and radios the requests come through — Called-Station-Id and NAS-Identifier from a month of the accounting log:

PS C:\> Invoke-Command -ComputerName nps1 -ScriptBlock {
    $log = Get-ChildItem C:\Windows\System32\LogFiles\IN*.log | Sort-Object Name | Select-Object -Last 1
    Get-Content $log.FullName | ForEach-Object { ([xml]$_).Event } |
        Where-Object { $_.'Packet-Type'.'#text' -eq '1' -and $_.'NAS-Port-Type'.'#text' -eq '19' } |
        ForEach-Object { [PSCustomObject]@{ Client = $_.'Client-Friendly-Name'.'#text'; NasId = $_.'NAS-Identifier'.'#text'; Called = $_.'Called-Station-Id'.'#text' } }
} | Group-Object Client, NasId, Called -NoElement | Sort-Object Name | Select-Object Count, Name

Count Name
----- ----
  110 ap-floor1, aabbcc1d0001, AA-BB-CC-1D-00-01:CorpWiFi
 2057 ap-floor1, aabbcc1e0001, AA-BB-CC-1E-00-01:CorpWiFi
 1370 ap-floor2, aabbcc1e0002, AA-BB-CC-1E-00-02:CorpWiFi
   26 ap-floor3, aabbcc1d0003, AA-BB-CC-1D-00-03:CorpWiFi
 1659 ap-floor3, aabbcc1e0003, AA-BB-CC-1E-00-03:CorpWiFi

NAS-Port-Type 19 is Wireless-802.11. ap-floor1 and ap-floor3 have two BSSIDs each — two radios — and these APs put the BSSID into NAS-Identifier too; the SSID after the colon is the same in every line, and that's what the .*:CorpWiFi$ condition checks.

What a connection costs — the packets of each conversation grouped by Acct-Session-Id, and the PEAP fast reconnect flag that NPS writes into the Access-Accept:

PS C:\> Invoke-Command -ComputerName nps1 -ScriptBlock {
    $log = Get-ChildItem C:\Windows\System32\LogFiles\IN*.log | Sort-Object Name | Select-Object -Last 1
    Get-Content $log.FullName | ForEach-Object { ([xml]$_).Event } |
        Where-Object { $_.'NP-Policy-Name'.'#text' -eq 'Secure Wireless Connections' } |
        Group-Object { $_.'Acct-Session-Id'.'#text' } | ForEach-Object {
            $accept = @($_.Group | Where-Object { $_.'Packet-Type'.'#text' -eq '2' })
            if ($accept.Count -eq 1) { [PSCustomObject]@{
                Requests   = @($_.Group | Where-Object { $_.'Packet-Type'.'#text' -eq '1' }).Count
                Challenges = @($_.Group | Where-Object { $_.'Packet-Type'.'#text' -eq '11' }).Count
                FastReconnect = $accept[0].'PEAP-Fast-Roamed-Session'.'#text' } } }
} | Group-Object Requests, Challenges, FastReconnect -NoElement | Sort-Object Count -Descending | Select-Object -First 5 Count, Name

Count Name
----- ----
  283 13, 12, 0
  128 5, 4, 1
    2 25, 24, 0
    1 14, 13, 0
    1 23, 22, 0

Two kinds of connection. A full one is 13 Access-Requests and 12 Access-Challenges before the Access-Accept: identity, PEAP start, the TLS handshake in which the certificate chain (about 2.8 KB) goes out in several fragments at a Framed-MTU of 1002 and the client acknowledges each with an empty response, then MSCHAPv2 and cryptobinding inside the tunnel. Fast reconnect (PEAP-Fast-Roamed-Session = 1) is 5 and 4: the TLS session is resumed, the certificate and MSCHAPv2 aren't repeated. Almost a third of the month's connections went that way — that's FastReconnect in the profile at work. In the rare long lines (24 and 22 Challenges) two exchanges fit under one Acct-Session-Id: the first broke off, the second ended with an Accept.

And why the policies need the computer. This server has no policy for Domain Computers, while laptops with the default profile (machineOrUser) authenticate with the computer account before a user signs in:

PS C:\> Invoke-Command -ComputerName nps1 -ScriptBlock {
    $log = Get-ChildItem C:\Windows\System32\LogFiles\IN*.log | Sort-Object Name | Select-Object -Last 1
    Get-Content $log.FullName | ForEach-Object { ([xml]$_).Event } |
        Where-Object { $_.'SAM-Account-Name'.'#text' -like '*$' } |
        ForEach-Object { [PSCustomObject]@{
            Time    = $_.Timestamp.'#text'
            Packet  = $_.'Packet-Type'.'#text'
            User    = $_.'User-Name'.'#text'
            Account = $_.'SAM-Account-Name'.'#text'
            Policy  = $_.'NP-Policy-Name'.'#text'
            Reason  = $_.'Reason-Code'.'#text' } }
} | Select-Object -First 2 | Format-Table Time, Packet, User, Account, Policy, Reason

Time                    Packet User                       Account    Policy                              Reason
----                    ------ ----                       -------    ------                              ------
10/21/2026 17:39:50.585 1      host/lap1.corp.example.com CORP\LAP1$ Connections to other access servers 0
10/21/2026 17:39:50.585 3                                 CORP\LAP1$ Connections to other access servers 65

The client called itself host/lap1.corp.example.com, NPS found the account CORP\LAP1$, but it's in Domain Computers, not Domain Users. The working policy didn't match, the request fell through to "Connections to other access servers" and was rejected with code 65 within the same millisecond — on the very first Access-Request, before PEAP began. The user who signed in a couple of minutes later connected, but this laptop doesn't get its computer policies over Wi-Fi at startup. The cure is the "Wi-Fi — Domain Computers" policy from the table above.


Sources

802.1X and RADIUS:

  • RFC 3580 — RADIUS for 802.1X: the "MAC:SSID" format of Called-Station-Id, VLANs via tunnel attributes, Session-Timeout and Termination-Action;
  • RFC 2868 — the tunnel attributes Tunnel-Type, Tunnel-Medium-Type, Tunnel-Private-Group-ID;
  • RFC 2548 — MS-MPPE-Recv-Key and MS-MPPE-Send-Key, from which the AP takes the PMK;
  • RFC 3748 and RFC 3579 — EAP and carrying it in RADIUS;
  • RFC 1334 — PAP, which MAB arrives with.

EAP methods and the Windows client:

Group Policy and deployment:

NPS:


Inside the office the network lets in only your own people. What's left is letting your own people in from outside — again through NPS, with the same accounts, but over a different protocol and through a gateway that sits at the edge.

Next — part 28: remote access VPN. An IKEv2 gateway on strongSwan that doesn't check passwords itself but hands EAP to NPS over RADIUS; how EAP travels inside IKE_AUTH and why it's EAP-MSCHAPv2 without PEAP here; an NPS policy for the Virtual port type, a swanctl configuration with eap-radius and address pools, the gateway certificate, the VPN profile on a Windows client, and what from part 19 of the networking series about IPsec and IKEv2 works here unchanged.

Only registered users can leave a comment.

Sign in or register to leave a comment.