Eén wachtwoord voor het hele huis

Een WiFi-wachtwoord wordt gedeeld door alles wat ooit verbinding heeft gemaakt. De laptops, de telefoons, de tv, de deurbelcamera met firmware die niemand heeft bijgewerkt sinds hij is opgehangen, de gast die het met kerst kreeg, en elke telefoon die die gast sindsdien heeft gehad. Ze zitten allemaal op hetzelfde netwerk. Elk van hen kan met elk ander praten.

Dat opsplitsen gaat meestal met meer SSID’s. Eén voor personeel, één voor IoT, één voor gasten, elk met een eigen wachtwoord en een eigen VLAN. Het werkt. Dan lekt er een wachtwoord, en loop je langs elk apparaat op die SSID met een nieuw wachtwoord, en dat is precies het klusje dat nooit gebeurt.

Een vrije netwerkpoort is erger. Helemaal geen wachtwoord. Steek de kabel erin en je zit op het VLAN waarop de poort is ingesteld.

802.1X met certificaten draait dat om. Elk apparaat heeft een eigen certificaat en bewijst wat het is voordat er ook maar één pakket doorkomt, en daarna beslist het netwerk waar het heen gaat, niet het apparaat en niet degene die het wachtwoord uitdeelde. Eén SSID. Elke switchpoort hetzelfde. De laptop komt op het personeels-VLAN, de camera op het IoT-VLAN, en een apparaat waaraan je nooit een certificaat hebt uitgegeven komt nergens.

Eén gedeeld wachtwoordEén SSID per VLAN802.1X, één SSID
Wie komt erinIedereen met het wachtwoordIedereen met het wachtwoord van die SSIDAlleen een apparaat met een certificaat dat jij hebt uitgegeven
Welk VLANAllemaal op éénWelke SSID ze ook kozenWat de RADIUS-server voor dat apparaat zegt
Eén kwijtgeraakt apparaat buitensluitenOveral het wachtwoord wijzigenOveral het wachtwoord van die SSID wijzigenEén certificaat intrekken
Bekabelde poortenOpenOpenDezelfde controle als WiFi
Uit te zenden SSID’s1Eén per VLAN1

Dat is wat dit bericht bouwt. step-ca geeft de certificaten uit en schrijft de rol van elk apparaat erin, FreeRADIUS controleert elk certificaat tegen de CA en haar intrekkingslijst, leest de rol eruit en antwoordt met een VLAN, en de switch of het access point zet het apparaat waar het moet staan. De apparatuur is Netgear of TP-Link, en praat waar dat kan via TLS met FreeRADIUS. Alles draaide hier voordat er iets in de buurt van echte apparatuur kwam: de CA en de RADIUS-server in containers, en de switchpoort en het WiFi-netwerk in simulators.

Vier onderdelen, en maar één beslist

802.1X noemt drie rollen1. De supplicant is het apparaat dat erbij wil. De authenticator is de switchpoort of het access point. De authenticatieserver is RADIUS. Tel daar de CA bij op die de certificaten uitgaf en je hebt het hele systeem.

Wie doet wat bij een 802.1X-aanmeldingApparaatsupplicantSwitch of APauthenticatorgeeft door, beslist nietsFreeRADIUSkiest het VLANstep-cacerts en CRLEAP-TLSpoort gaat openRadSecVLAN 10CRL, HTTPScertificaat, één keer uitgegeven
De switch of het AP geeft alleen door. EAP gaat van het apparaat naar FreeRADIUS, binnen EAPOL en daarna binnen RADIUS, en het antwoord komt terug met het VLAN in drie attributen. step-ca ziet nooit een aanmelding; die ondertekende de certificaten vooraf en publiceert de intrekkingslijst.

De switch en het AP beslissen niets. Ze geven door. Tot het apparaat zich heeft aangemeld laat de poort EAP door en verder niets. De authenticator verpakt die EAP in RADIUS, stuurt het naar de server en handelt naar het antwoord. Een Access-Accept zet de poort open. Bevat de Accept tunnelattributen, dan gaat de poort of de WiFi-client op het VLAN dat ze noemen. RFC 3580 legt vast welke drie attributen dat zijn, geleend van de tunnelattributen van RADIUS23:

AttribuutWaardeBetekenis
Tunnel-TypeVLAN (13)Deze tunnel is een VLAN
Tunnel-Medium-TypeIEEE-802 (6)Op een 802-netwerk
Tunnel-Private-Group-Id"10"Het VLAN-ID, verstuurd als tekenreeks

Het merk van de switch doet er dus minder toe dan je zou denken. De beslissing ligt op een machine die jij beheert. Elke fabrikant die dynamische VLAN’s doet leest dezelfde drie attributen. De RFC zegt ronduit dat het VLAN-ID “encoded as a string”3 wordt, en daarom staat het tussen aanhalingstekens.

Waarom certificaten, en waarom alleen TLS 1.3

Er bestaan meerdere EAP-methoden, en de meeste die in gebruik zijn, zijn een wachtwoord verpakt in TLS. Voor een mens werkt dat. Voor een camera is het nutteloos, want er is niemand om iets in te typen, en een wachtwoord dat door duizend camera’s wordt gedeeld is gewoon weer het WiFi-wachtwoord.

MethodeWat het apparaat bewijstAls het apparaat de verkeerde server vertrouwtEén apparaat buitensluiten
EAP-TLSZijn eigen certificaat en privésleutelDe server leert het certificaat, dat toch al openbaar isHet certificaat intrekken
PEAP met MS-CHAPv2Een gebruikersnaam en wachtwoordDe server vangt een handshake die terug te kraken is tot het wachtwoordHet wachtwoord wijzigen
EAP-TTLS met PAPEen gebruikersnaam en wachtwoordDe server krijgt het wachtwoord zelf toegestuurdHet wachtwoord wijzigen

Die middelste kolom is de reden voor EAP-TLS. MS-CHAPv2 is al meer dan tien jaar gekraakt. Microsofts eigen advies zegt dat “an attacker who obtained the MS-CHAP v2 authentication traffic could then use the exploit code to decrypt a user’s credentials”4. Het in PEAP verpakken beschermt het alleen als elk apparaat controleert met wie het praat, en zoals het deel over apparaten laat zien, doen genoeg apparaten dat niet. Bij EAP-TLS zit er geen geheim in de uitwisseling om te stelen. De privésleutel verlaat het apparaat nooit5. Niets om te kraken.

TLS 1.2 zet het certificaat in de lucht

EAP-TLS over TLS 1.2 heeft een fout die je makkelijk mist. Het apparaat stuurt zijn certificaat voordat de versleuteling begint. Onversleuteld. Iedereen die naar de WiFi luistert kan het lezen, en omdat dit bericht de rol van het apparaat in het certificaat zet, kunnen ze die ook lezen.

Hier gemeten, door elk EAP-bericht dat het apparaat verstuurde te doorzoeken op de naam en de rol van het apparaat:

RunTLSApparaatnaam onversleuteldRol OU=staff onversleuteld
Identiteit laptop11.2JaJa
Identiteit laptop11.3Ja, in de identiteitNee
Identiteit anonymous1.3NeeNee

TLS 1.3 lost het op. RFC 9190 zegt dat met TLS 1.3 “the use of privacy is mandatory and does not cause any additional round trips”6. De buitenste identiteit gaat nog steeds onversleuteld, dus ook die hoort het apparaat niet te noemen. De RFC raadt een anonieme identiteit aan6. Niets hier heeft een echte nodig, want FreeRADIUS leest uit het certificaat wie het apparaat is. Met anonymous aan de buitenkant is de laatste rij wat een afluisteraar krijgt: niks.

Deze opzet draait dus alleen TLS 1.3. FreeRADIUS staat er aan beide kanten op vast. Een apparaat dat het niet spreekt komt er niet op. Dat heeft een prijs, en die wordt in het deel over apparaten geteld in plaats van weggestopt. De eigen config van FreeRADIUS zegt nog steeds dat je op 1.2 moet afkappen, in hoofdletters, omdat “most EAP supplicants WILL NOT DO TLS 1.3 PROPERLY”7. Eer wie eer toekomt: die config is de eerlijkste documentatie in dit hele bericht, en bijna elke valkuil hieronder staat er ergens in, als je de commentaarregels leest. Op een netwerk waar jij de apparaten kiest, is dat een reden om betere apparaten te kiezen.

De rol in het certificaat zetten

Het VLAN wordt bepaald door wat het apparaat is, en daar is het certificaat voor. step-ca kan het erin schrijven met een sjabloon, hetzelfde mechanisme dat het bericht over clientcertificaten gebruikt voor het CRL-distributiepunt. Het opstarten van de CA zelf staat in step-ca in een container, en dit gaat daar verder met een CA die Home Network CA heet.

Elk apparaatcertificaat krijgt zijn rol in de organisatie-eenheid (OU) van zijn subject. Het sjabloon haalt de rol uit het verzoek en weigert alles wat niet op de lijst staat8:

{{- $roles := list "staff" "iot" "media" }}
{{- if not (has .Insecure.User.role $roles) }}
  {{- fail "role must be one of staff, iot or media" }}
{{- end }}
{
  "subject": {
    "commonName": {{ toJson .Subject.CommonName }},
    "organizationalUnit": {{ toJson .Insecure.User.role }}
  },
  "sans": {{ toJson .SANs }},
  "keyUsage": ["digitalSignature"],
  "extKeyUsage": ["clientAuth"],
  "crlDistributionPoints": ["http://ca.home.example/1.0/crl"]
}

Geef het een eigen provisioner, zodat apparaten nooit van de algemene admin komen, met een jaar als langste certificaat dat hij ondertekent:

step ca provisioner add network --type JWK --create \
  --x509-max-dur 8760h --x509-default-dur 24h \
  --x509-template templates/device.tpl

--x509-template kopieert het sjabloon naar ca.json in plaats van naar het bestand te verwijzen, dus device.tpl achteraf bewerken verandert niets tot je step ca provisioner update network --x509-template templates/device.tpl opnieuw draait. Geef daarna voor elk apparaat het certificaat uit met zijn rol:

step ca certificate laptop1 laptop1.crt laptop1.key \
  --provisioner network --not-after 8760h --set role=staff
$ openssl x509 -in laptop1.crt -noout -subject -ext extendedKeyUsage
subject=OU=staff, CN=laptop1
X509v3 Extended Key Usage:
    TLS Web Client Authentication

Een ontbrekende of onbekende rol krijgt niets:

$ step ca certificate norole norole.crt norole.key --provisioner network
The request could not be completed: role must be one of staff, iot or media.

.Insecure.User heet onveilig omdat het is wat de aanvrager meestuurt. Hier is dat prima. De network-provisioner heeft een wachtwoord, alleen jij hebt het, en wie ermee kan ondertekenen kan toch al elk certificaat maken dat hij wil, dus een rol die hij zelf heeft ingetypt geeft hem niets extra.

De RADIUS-server heeft ook een certificaat nodig, zodat apparaten kunnen controleren met wie ze praten. Het komt van de admin-provisioner zoals elk ander servercertificaat, en leeft de standaard 24 uur, vernieuwd door step ca renew --daemon zoals het step-ca-bericht dat doet voor elke dienst die geen ACME kan. Apparaten controleren de naam en de CA, niet het serienummer. Een dagelijkse vernieuwing verandert voor hen niets.

CertificaatProvisionerLooptijdExtended key usageBevat
radius.home.exampleadmin24 uur, dagelijks vernieuwdServer en clientDe naam die apparaten controleren
laptop1network1 jaarAlleen clientOU=staff
cam1network1 jaarAlleen clientOU=iot
tv1network1 jaarAlleen clientOU=media
switch1, ap1nas1 jaarAlleen clientOU=nas, voor RadSec; zie verderop

FreeRADIUS: het VLAN bepalen

FreeRADIUS 3.2.10, in het eigen containerimage van het project. Drie stukken config doen het werk: de EAP-module, een policy die het VLAN kiest, en een lijst met uitzonderingen.

De EAP-module

Alleen EAP-TLS, alleen TLS 1.3, en de CRL bij elke aanmelding gecontroleerd:

eap {
	default_eap_type = tls

	tls-config tls-common {
		certificate_file = ${certdir}/eaptls/radius.crt
		private_key_file = ${certdir}/eaptls/radius.key
		ca_path = ${certdir}/eaptls/ca
		ca_file = ${certdir}/eaptls/ca/root_ca.crt

		check_crl = yes
		ca_path_reload_interval = 300

		tls_min_version = "1.3"
		tls_max_version = "1.3"
	}

	tls {
		tls = tls-common
	}
}
BestandBevat
radius.crtHet servercertificaat, dan de intermediate van step-ca, zoals step ca certificate het wegschrijft
radius.keyDe privésleutel; zoals alles hier leesbaar voor de gebruiker freerad, anders start FreeRADIUS niet
ca/De root, de intermediate en de CRL van de intermediate, gehasht met openssl rehash ca/

Die middelste rij hield hier de eerste start tegen, al was het niet om de sleutel: Permission denied op het rootcertificaat, omdat elk bestand uit de CA was gekomen met alleen leesrecht voor de eigenaar. FreeRADIUS draait als freerad, dus alle drie moeten voor die gebruiker leesbaar zijn.

check_crl = yes controleert het certificaat van het apparaat tegen de CRL van de intermediate. De intermediate zelf controleert het niet, dus anders dan nginx in het bericht over clientcertificaten heeft FreeRADIUS geen root-CRL nodig. check_all_crl zet dat aan als je het wilt7. ca_path_reload_interval is belangrijker dan het lijkt, en heeft verderop een eigen hoofdstuk.

De policy

Elk certificaat dat deze CA ooit heeft uitgegeven zou door de TLS-controle komen. Ook die van de webservers die het hunne via ACME kregen. De policy gooit dus alles eruit wat niet voor deze taak is gemaakt: geen client-auth-gebruik, of geen rol, en het wordt geweigerd.

Hoe FreeRADIUS het VLAN kiestTLS 1.3-handshake, CRLClient-auth-gebruik?Op de apparatenlijst?Rol in het certificaat?Geen rol: weigerenWeigerenLijst wint: cam1, VLAN 99staff, 10iot, 20media, 30janeegeenneeja
De lijst per apparaat wordt eerst gevraagd en wint als die een antwoord heeft. Anders kiest de rol in het certificaat het VLAN. Een certificaat zonder een van beide wordt geweigerd, en dat houdt de webservercertificaten van de CA van het netwerk.
vlan_by_certificate {
	if (&TLS-Client-Cert-X509v3-Extended-Key-Usage-OID !~ /1\.3\.6\.1\.5\.5\.7\.3\.2/) {
		reject
	}

	device_vlan

	if (!&reply:Tunnel-Private-Group-Id) {
		if (&TLS-Client-Cert-Subject =~ /\/OU=([^\/]+)/) {
			switch "%{1}" {
				case "staff" {
					update reply { &Tunnel-Private-Group-Id := "10" }
				}
				case "iot" {
					update reply { &Tunnel-Private-Group-Id := "20" }
				}
				case "media" {
					update reply { &Tunnel-Private-Group-Id := "30" }
				}
				case {
					reject
				}
			}
		}
		else {
			reject
		}
	}

	update reply {
		&Tunnel-Type := VLAN
		&Tunnel-Medium-Type := IEEE-802
	}
}

Dat komt in policy.d/vlan, en één regel, vlan_by_certificate, komt bovenaan de post-auth-sectie van sites-available/default. In post-auth zijn de gegevens van het certificaat beschikbaar, als de TLS-handshake klaar is9.

De uitzonderingslijst

De rol bepaalt de meeste apparaten. Niet alle. De lijst is voor de paar waarvoor dat niet moet: een camera die zich vreemd is gaan gedragen en in quarantaine moet voordat je uitzoekt waarom, of een apparaat dat een week is uitgeleend. Het is een passwd-module die een bestand met twee kolommen leest, met de common name van het certificaat als sleutel:

passwd device_vlan {
	filename = ${modconfdir}/device_vlan/devices
	format = "*TLS-Client-Cert-Common-Name:=Tunnel-Private-Group-Id"
	delimiter = ","
}
# certificate CN,VLAN
cam1,99

De = voor het attribuut zet het in het antwoord in plaats van in de check items10. Een apparaat verplaatsen is één regel in een tekstbestand, zonder certificaat om opnieuw uit te geven en zonder iets op het apparaat aan te raken.

VLANRolWat er woont
10staffLaptops en telefoons die je vertrouwt
20iotCamera’s, sensoren, alles wat naar huis belt
30mediaTv’s en streamers
99Alleen via lijstQuarantaine: hooguit internet, niets lokaals

Het bewijzen voordat je een switch aanraakt

eapol_test zit bij wpa_supplicant en speelt tegelijk een apparaat en een switch, rechtstreeks pratend met de RADIUS-server. Gebruik het eerst. Als er later iets niet werkt, weet je welke helft je moet bekijken.

network={
    key_mgmt=WPA-EAP
    eap=TLS
    phase1="tls_disable_tlsv1_3=0"
    identity="anonymous"
    ca_cert="/etc/pki/home/root_ca.crt"
    domain_match="radius.home.example"
    client_cert="/etc/pki/home/laptop1.crt"
    private_key="/etc/pki/home/laptop1.key"
}
eapol_test -c laptop1.conf -a 192.0.2.10 -s <shared secret>

De antwoorden, uit het eigen debuglog van FreeRADIUS:

(8) Sent Access-Accept
(8)   Tunnel-Private-Group-Id = "10"
(8)   Tunnel-Type = VLAN
(8)   Tunnel-Medium-Type = IEEE-802
ApparaatRol in certificaatOp de lijstAntwoordVLAN
laptop1staffNeeAccept10
cam1iot99Accept99
tv1mediaNeeAccept30
laptop2staff, ingetrokkenNeeReject, certificate revokedGeen
radius.home.exampleGeenNeeReject, geen rolGeen
laptop1 met TLS 1.3 uitstaffNeeReject, protocol versionGeen

Die laatste rij is goed om te weten voordat je live gaat. wpa_supplicant 2.11 wordt nog steeds geleverd met TLS 1.3 uitgeschakeld voor EAP, omschreven als “experimental - disabled by default”11. Laat de phase1-regel weg en een prima laptop wordt geweigerd.

Een switchpoort, gesimuleerd

eapol_test slaat de switch over. Om de helft van de switch te zien heeft hostapd een bekabelde driver die een Linux-interface in een 802.1X-poort verandert. Een virtuele kabel verbindt die met een wpa_supplicant die de laptop speelt, en hij wijst naar dezelfde FreeRADIUS.

interface=port1
driver=wired
ieee8021x=1
use_pae_group_addr=1
auth_server_addr=192.0.2.10
auth_server_port=1812
auth_server_shared_secret=<shared secret>

Hoe de poort een goede laptop en een ingetrokken laptop ziet:

port1: CTRL-EVENT-EAP-SUCCESS2 4a:42:cf:cf:09:5a
port1: AP-STA-CONNECTED 4a:42:cf:cf:09:5a
port1: STA 4a:42:cf:cf:09:5a IEEE 802.1X: authorizing port

port1: CTRL-EVENT-EAP-FAILURE2 de:d8:8c:88:6e:f2
port1: IEEE 802.1X: Force disconnection of de:d8:8c:88:6e:f2 after EAP-Failure in 10 ms

Zet de afhandeling van dynamische VLAN’s aan en hij meldt het VLAN dat hij kreeg, vlan=10 voor de laptop en vlan=99 voor de camera in quarantaine, en zegt dan could not add dynamic VLAN interface. Dat is een Linux-poort die eerlijk is: hij kan open en dicht, maar zichzelf niet op een VLAN zetten. Een managed switch doet dat deel in hardware, en dat is de helft die de instellingen van de fabrikanten hieronder behandelen.

Intrekking: de CRL die FreeRADIUS negeert bij een HUP

Een kwijtgeraakte laptop is het geval waarvoor dit allemaal bestaat. Trek hem in bij de CA. De admin-provisioner ondertekent een intrekkingstoken voor het serienummer, en dat werkt zonder de sleutel van de laptop, want de laptop heb je niet meer:

SERIAL=$(step certificate inspect laptop2.crt --format json | jq -r .serial_number)
TOKEN=$(step ca token "$SERIAL" --revoke --provisioner admin)
step ca revoke "$SERIAL" --token "$TOKEN" --reason "laptop lost"
Certificate with Serial Number 306546036599704679037436836531698019860 has been revoked.

step-ca bouwt zijn CRL meteen opnieuw als generateOnRevoke aanstaat, ingericht zoals het bericht over clientcertificaten laat zien. Kopieer hem naar de map ca/ van FreeRADIUS en de laptop ligt eruit. Alleen niet echt. Hij liep zo weer naar binnen.

Wat er gedaan werd nadat de nieuwe CRL op schijf stondIngetrokken laptop
NietsKomt er nog op
kill -HUPKomt er nog op. Het log zegt HUP - No files changed. Ignoring
Gewacht tot voorbij ca_path_reload_intervalGeweigerd, certificate revoked
FreeRADIUS herstartGeweigerd, certificate revoked

Een HUP herlaadt configuratiebestanden, en een CRL in ca_path is daar geen van, dus FreeRADIUS controleert zijn lijst met configbestanden, vindt er geen gewijzigd en gaat door met de oude CRL in het geheugen. Wat hem wel oppikt is ca_path_reload_interval. In de standaardconfig staat dat uit. Zet het onder 300 seconden en FreeRADIUS overschrijft je bij het opstarten:

ca_path_reload_interval is set too low, reset it to 300

Dan wordt het 300. Een intrekking werkt binnen vijf minuten nadat de nieuwe CRL is geland. Haal hem op met een timer, zoals het bericht over clientcertificaten al doet voor nginx, maar dan naar deze map. Herstart FreeRADIUS als vijf minuten te lang is voor het apparaat dat je net kwijt bent.

De CRL kan ook iedereen tegelijk buitensluiten. Dus beide kanten op. Beide gevallen zijn getest met goede certificaten:

Toestand CRLWat FreeRADIUS zegtWie komt erop
ActueelNietsElk goed apparaat
Ontbreekt in ca/error 3 : unable to get certificate CRLNiemand
Voorbij zijn next-update-tijderror 12 : CRL has expiredNiemand

De CRL van step-ca is standaard 24 uur geldig. Stopt de timer die hem ophaalt, dan faalt de volgende dag elk apparaat op het netwerk. allow_expired_crl bestaat om dat te verzachten7. Gebruik het niet. Een netwerk dat iedereen binnenlaat omdat een lijst verouderd is, controleert niets meer, en als zodanig is de oplossing de timer bewaken, niet de controle uitzetten.

RADIUS is nog steeds MD5 en een gedeeld geheim

Alles tot nu toe beschermt het gesprek tussen het apparaat en FreeRADIUS. EAP-TLS loopt van begin tot eind, en de switch of het AP in het midden kan het niet lezen. Het RADIUS-pakket dat het draagt is een ander verhaal.

Klassieke RADIUS is UDP met een gedeeld geheim, en dat geheim wordt via MD5 gebruikt. Dat zou een stuk minder uitmaken als het pakket niets waardevols droeg, maar de Access-Accept voor een WiFi-client draagt de sleutels tot het verkeer van die client. MS-MPPE-Recv-Key en MS-MPPE-Send-Key zijn de hoofdsleutel waarmee het AP het verkeer van die client versleutelt, en RFC 2548 beschermt ze met b(1) = MD5(S + R + A) en een XOR, waarbij S het gedeelde geheim is12. Daar zit geen forward secrecy in. Vang vandaag het RADIUS-verkeer op, haal volgend jaar het geheim uit een configback-up of de running config van een switch, en elke opgevangen sleutel ontsleutelt, samen met het WiFi-verkeer dat hij beschermde.

De vraag is dus niet of het geheim lang genoeg is. De vraag is waarom de sleutels van het draadloze netwerk überhaupt onder MD5 over de kabel gaan. Dat hoeft niet.

DreigingGewone RADIUS over UDPMet Message-Authenticator verplichtRadSec, TLS 1.3, wederzijdse certificaten
Vervalste Access-Accept (BlastRADIUS)Mogelijk bij verzoeken zonder EAP, MAB inbegrepenGestoptGestopt
WiFi-sleutels uit een opname gelezenIedereen die ooit het geheim te weten komtHetzelfdeVereist de TLS-sessiesleutels, die nooit worden opgeslagen
Een machine op het beheernetwerk die zich als switch voordoetHeeft alleen het geheim nodigHetzelfdeHeeft een certificaat met de rol nas nodig, vanaf een bekend adres
Wie ziet welk apparaat zich aanmelddeIedereen op het padHetzelfdeNiemand

BlastRADIUS, en de regel die het stopt

BlastRADIUS, CVE-2024-3596, laat een aanvaller op het pad tussen switch en server een weigering omzetten in een toegang, door de MD5 aan te vallen die het antwoord ondertekent13. De oplossing is het attribuut Message-Authenticator op elk verzoek en elk antwoord13. RFC 3579 heeft het altijd verplicht gesteld op alles wat EAP draagt14, dus EAP-TLS was nooit de zwakke plek. MAB wel, want een MAC-adrescontrole draagt geen EAP en genoeg switches stuurden hem zonder.

FreeRADIUS 3.2.10 eist het standaard, met require_message_authenticator = yes in radiusd.conf. Hier getest met een handgemaakt MAB-verzoek:

VerzoekAntwoord
Geen Message-AuthenticatorGeen antwoord. Gelogd als Packet does not contain required Message-Authenticator attribute
Met Message-AuthenticatorEen antwoord, Access-Reject voor een onbekend MAC-adres

Laat die instelling zoals ze is. De config waarschuwt dat die instelling en limit_proxy_state allebei op no zetten “will allow MITM attackers to create fake Access-Accept packets to the NAS”15.

Dat sluit de vervalsing af. Voor de sleutels doet het niets.

RadSec: RADIUS binnen TLS 1.3

De hoogste beschikbare standaard is RadSec, RADIUS over TLS met aan elke kant een certificaat16. De switch of het AP bewijst met een certificaat wie hij is, FreeRADIUS bewijst met zijn eigen certificaat wie hij is, en het hele RADIUS-gesprek, sleutels inbegrepen, zit binnen TLS 1.3 met forward secrecy. De IETF zet het op het standaardenspoor; de draft die de experimentele RFC uit 2012 zou vervangen staat op “Has enough positions to pass”17.

De switches en AP’s krijgen certificaten van step-ca zoals al het andere, van een derde provisioner waarvan het sjabloon OU=nas erin zet en verder niets. Daardoor kan een apparaatcertificaat niet worden gebruikt om zich als switch voor te doen, en kan een switchcertificaat, zonder rol die de VLAN-policy herkent, ook niet worden gebruikt om op het netwerk te komen.

listen {
	ipaddr = *
	port = 2083
	type = auth
	proto = tcp
	virtual_server = default
	clients = radsec

	tls {
		certificate_file = ${certdir}/eaptls/radius.crt
		private_key_file = ${certdir}/eaptls/radius.key
		ca_path = ${certdir}/eaptls/ca
		ca_file = ${certdir}/eaptls/ca/root_ca.crt

		require_client_cert = yes
		check_crl = yes
		ca_path_reload_interval = 300

		verify {
			tmpdir = ${run_dir}
			client = "${raddbdir}/nas-only.sh %{TLS-Client-Cert-Filename}"
		}

		tls_min_version = "1.3"
		tls_max_version = "1.3"
	}
}

clients radsec {
	client switch1 {
		ipaddr = 192.0.2.21
		proto = tls
		secret = radsec
	}
}
#!/bin/sh
# Accept a RadSec client certificate only if step-ca issued it with the nas role.
openssl x509 -in "$1" -noout -subject -nameopt compat | grep -q '/OU=nas/'

Het geheim is echt het woord radsec. Eerlijk waar. Over TLS doet het niets, en het eigen voorbeeld van FreeRADIUS zegt “TLS clients MUST have the shared secret set to ‘radsec’”18. Twee dingen die voor de hand liggen werken niet, allebei op de harde manier ontdekt. check_cert_cn = %{client:shortname} wordt tijdens de handshake tot niets uitgevouwen, dus elke switch wordt geweigerd met Certificate CN (switch1) does not match specified value (); vandaar het verify-script. En de listener start niet onder freeradius -X, dat threads uitzet: Threading must be enabled for TLS sockets to function properly. Debug hem in plaats daarvan met freeradius -fxx -l stdout.

Elke hop naar FreeRADIUS beschermenFreeRADIUSpoort 2083alleen OU=nasOmada, hostapdRadSec ingebouwdRadSec, TLS 1.3Netgearalleen UDPradsecproxyzelfde beheer-VLANUDP+ Msg-AuthRadSecstep-cageeft elk cert uitCRL via HTTPS, root vastgepind
Waar elke hop beschermd is. Apparatuur die RadSec spreekt gaat rechtstreeks naar FreeRADIUS over TLS 1.3. Apparatuur die alleen UDP spreekt praat met radsecproxy op hetzelfde beheer-VLAN, en alleen die korte hop is gewone RADIUS. De CRL komt van step-ca over HTTPS met de root vastgepind.

De gesimuleerde switchpoort van eerder, omgebouwd naar RadSec, tegen dezelfde tests:

Switch toontVanafResultaat
switch1, OU=nasZijn vermelde adresTLS 1.3 op; laptop1 toegelaten op VLAN 10, ingetrokken laptop2 geweigerd
Het certificaat van laptop1Het adres van de switchGeweigerd in de handshake; het verify-script geeft 1 terug
switch1, OU=nasEen niet vermeld adresIgnoring request … from unknown client

Welke apparatuur het kan

AuthenticatorRadSecOpmerkingen
hostapd 2.11Ja, als hij ermee is gebouwdCONFIG_RADIUS_TLS=y staat uitgecommentarieerd in zijn defconfig19. Het pakket van Alpine laat het uit en weigert de config: unsupported RADIUS type 'TLS'. Ermee gebouwd werkte het in één keer
TP-Link Omada AP’s en switchesVanaf Controller 6.1, met actuele firmware“Added support for RadSec (RADIUS over TLS) to secure SSID, MAC-Based, and 802.1X authentications on both APs and Switches”20
TP-Link Omada RADIUS-proxyNeeDe eigen proxy van de controller “does not support the RadSec feature”21
Netgear M4250 en M4350Niet gedocumenteerdDe CLI-handleiding noemt RadSec nergens en begrenst het gedeelde geheim: “The secret must be an alphanumeric value not exceeding 16 characters”22
Netgear WAX- en WBE-AP’sNiet gedocumenteerdDe handleidingen beschrijven de RADIUS-poort als UDP, en het standaardgeheim als sharedsecret23

Mag ik vragen waarom een switch die in 2026 als volledig managed wordt verkocht niet via TLS met zijn RADIUS-server kan praten? Niet wie dat besloot. Wat in het proces liet het zo uitleveren, terwijl de controller van TP-Link, hostapd en een gratis proxy het allemaal kunnen. Zestien willekeurige alfanumerieke tekens is ongeveer 95 bits, en dat is niet wat er gekraakt wordt. Het punt is dat het de enige bescherming is die je krijgt: geen TLS-optie, een MD5-schema uit 1999 dat de WiFi-sleutels bewaakt, en een AP dat wordt geleverd met het geheim ingesteld op het woord sharedsecret. Op Netgear-apparatuur is radsecproxy niet optioneel.

radsecproxy, voor apparatuur die alleen UDP spreekt

Waar de switch of het AP geen RadSec kan, zet je radsecproxy ernaast. Die neemt UDP-RADIUS aan en stuurt RadSec door24, zodat de onbeschermde hop de paar meter beheer-VLAN tussen de switch en de proxy is, niet het hele pad naar de server:

ListenUDP *:1812

tls stepca {
    CACertificateFile  /etc/radsecproxy/root_ca.crt
    CertificateFile    /etc/radsecproxy/radsecproxy.crt
    CertificateKeyFile /etc/radsecproxy/radsecproxy.key
    TlsVersion TLS1_3
}

client switches {
    host 192.0.2.32/27
    type udp
    secret <long random secret>
    RequireMessageAuthenticator on
}

server freeradius {
    host radius.home.example
    port 2083
    type tls
    tls stepca
    secret radsec
    certificateNameCheck on
}

realm * {
    server freeradius
}

Gebruik de naam. Niet het adres. Met host 10.89.0.100 weigerde de proxy de server, terecht: No subjectaltname matching address 10.89.0.100. Met de naam kwam hij op als TLSv1.3 with cipher TLS_AES_256_GCM_SHA384, en dezelfde apparaten kregen er dezelfde antwoorden doorheen: laptop1 VLAN 10, cam1 99, tv1 30, laptop2 geweigerd. Een verzoek zonder Message-Authenticator stopte bij de proxy: ignoring request from client switches … missing required message-authenticator.

Houd die UDP-hop eerlijk. Kort, en afgeschermd. Het beheeradres van de switch en de proxy zitten op een beheer-VLAN waar geen enkel client-VLAN naartoe kan routeren, en de firewall zegt dat ook.

Ook het ophalen van de CRL

De CRL is ondertekend, dus niemand kan er een vervalsen. Ze kunnen wel een oudere afspelen die nog geldig is, en die verbergt een intrekking die sindsdien is gedaan. Haal hem van step-ca op over HTTPS met de eigen root vastgepind, in plaats van over gewone HTTP:

curl -fsS --cacert /etc/freeradius/certs/eaptls/ca/root_ca.crt --tlsv1.3 \
  -o /tmp/crl.der https://ca.home.example:9000/1.0/crl

Tegen elke andere root faalt hetzelfde commando met exitcode 60 van curl, en FreeRADIUS houdt de lijst die hij heeft.

Waar het heen gaat

RFC 9765, RADIUS/1.1, haalt MD5 helemaal uit RADIUS als het over TLS loopt: “the RADIUS shared secret is no longer used, and all MD5-based packet authentication and attribute obfuscation methods are removed”25. Het is Experimental. FreeRADIUS 3.2 heeft het achter een buildvlag, --with-radiusv11, “for testing interoperability”26, en het officiële 3.2.10-image is er niet mee gebouwd; de optie zit niet in de binary. RadSec met TLS 1.3 is de hoogste standaard die je vandaag kunt uitrollen, en RADIUS/1.1 is waar het daarna heen gaat.

Elke managed switch die de moeite waard is doet 802.1X met dynamische VLAN’s, en de instellingen komen neer op dezelfde vijf dingen, welk merk er ook op staat. Het verschil tussen deze twee zit in het detail, en één detail aan de kant van TP-Link verandert hoe je een bureau bekabelt.

InstellingNetgear M4250 en M4350TP-Link Omada-switches
RADIUS-serverSecurity > Management Security > RADIUS > Server Configuration27Een RADIUS Profile in de Omada-controller28
802.1X aanSecurity > Port Authentication > Basic > 802.1X Configuration: Dot1x Admin Mode, Authentication Admin Mode27Authentication > 802.1X, Authentication Type “Port Based”28
VLAN van RADIUSVLAN Assignment Mode, “The default is Disable”27“enable ‘VLAN Assignment’”28
Ontbrekend VLANNiet aangemaakt tenzij Dynamic VLAN Creation Mode aanstaat, ook standaard uit27“the switch will create the related VLAN automatically”29
ToegangspoortenControl Mode Auto802.1X Control mode Auto28
Uplinks, de poort van de RADIUS-server, AP-poortenForce Authorized802.1X uitgelaten: “Keep 802.1X authentication disabled on ports connected to the authentication server, the internet and the management computer”29

Maak in beide gevallen eerst de VLAN’s op de switch aan. Een VLAN dat bestaat omdat een aanmelding het tevoorschijn toverde, is een VLAN dat niemand heeft ingericht en waarop niemand een firewallregel heeft gezet.

TP-Links eigen handleiding zegt “VLAN Assignment takes effect only when the control type is Port Based”29. Port based betekent dat het eerste apparaat dat zich aanmeldt het VLAN voor de hele poort bepaalt. Zet een bureauswitch of een telefoon met een pc erachter op die poort, en het tweede apparaat rijdt mee op het VLAN van het eerste zonder ooit om een certificaat te worden gevraagd. Op een personeelspoort betekent dat: alles wat achter in die telefoon zit, zit op personeel.

Dus bij TP-Link: één apparaat per toegangspoort. Niets tussen de muur en het apparaat. De MAC-based modus van Netgear meldt elk apparaat op een poort apart aan; de handleiding van de GS710TUP beschrijft dat als het toestaan van “multiple supplicants connected to the same port”30.

MAB, voor de printer die dit allemaal niet kan

Sommige apparatuur heeft helemaal geen 802.1X-client. MAC Authentication Bypass laat de switch het MAC-adres van het apparaat naar RADIUS sturen als zowel gebruikersnaam als wachtwoord, en beide fabrikanten ondersteunen het samen met 802.1X op dezelfde poort. Netgear probeert eerst 802.1X en valt terug: “if a client tries 802.1X authentication… and authentication fails, the interface proceeds with MAB authentication”27. TP-Link stuurt het MAC-adres als “12 hexadecimal digits, all lowercase and no punctuation”29.

Een MAC-adres is geen identiteit. Iedereen kan zijn netwerkkaart met één commando op het MAC-adres van de printer zetten, vanaf een laptop die hij heeft meegenomen, staand naast de printer waarvan hij net de kabel heeft geleend. MAB krijgt dus een eigen lijst, en de policy geeft het nooit meer dan het IoT-VLAN of quarantaine:

mab {
	if (!&EAP-Message && &User-Name =~ /^[0-9a-f]{12}$/ && &User-Password == &User-Name) {
		mab_vlan
		if (ok && &reply:Tunnel-Private-Group-Id =~ /^(20|99)$/) {
			update control { &Auth-Type := Accept }
			update reply {
				&Tunnel-Type := VLAN
				&Tunnel-Medium-Type := IEEE-802
			}
		}
		else {
			reject
		}
	}
}

Dat komt als eerste in authorize, met een passwd-module mab_vlan die mac,vlan-regels leest, precies zoals de apparatenlijst. De certificaatpolicy wordt verpakt in if (&EAP-Message) zodat ze MAB met rust laat. Getest:

MAB-verzoekRegel op de lijstAntwoord
aabbccddeeff20Accept, VLAN 20
112233445566GeenReject
ccddeeff001110, het personeels-VLANReject. De policy zet een MAC-adres niet op personeel, wat de lijst ook zegt

Wie de printer nabootst, krijgt het VLAN van de printer en verder niets. Meer hoort een MAC-adres nooit op te leveren.

De Omada-switchinstellingen hierboven zijn ook gecontroleerd tegen een gesimuleerde Omada Software Controller, 6.2.14.11, in een container zonder hardware erachter. De Engelse interfaceteksten noemen elke instelling die hier is gebruikt: de optie RadSec, waarbij “a TLS-encrypted transmission tunnel will be established between the client and the RADIUS server”, “Enable VLAN Assignment for Wireless Network”, “Require Message-Authenticator”, “Limit Proxy-State”, en het importeren van het clientcertificaat en de sleutel voor RadSec. Een controller configureert; hij laat geen verkeer door, dus dit bewijst dat de instellingen in die versie bestaan en verder niets. Voor Netgear is er niets vergelijkbaars om te draaien, dus die kolom komt uit Netgears eigen handleidingen en is hier niet getest.

Het access point: één SSID, elk VLAN

Bij het access point betaalt de enkele SSID zich uit. Eén WPA-Enterprise-netwerk, en elke client wordt op het VLAN gezet dat RADIUS ervoor noemt. Het AP tagt het verkeer van die client met zijn VLAN en stuurt het de kabel op, dus de switchpoort die het AP voedt moet elk VLAN dragen dat RADIUS kan uitdelen, getagd. Laat er één weg en het apparaat meldt zich perfect aan en krijgt nooit een adres.

Eén SSID, gesimuleerd

Voordat ik het woord van welke fabrikant dan ook geloofde, draaide het geheel op gesimuleerde radio’s. Linux’ mac80211_hwsim maakt virtuele WiFi-kaarten die echt WPA-Enterprise doen. De kernel van deze machine levert de module niet mee, dus de test startte dezelfde kernel in een kleine virtuele KVM-machine en laadde hem daar, uit Fedora’s eigen ondertekende pakket, met hostapd 2.11 als access point en drie wpa_supplicant-stations in hun eigen netwerknamespaces31:

interface=wlan0
driver=nl80211
ssid=Home
ieee8021x=1
wpa=2
wpa_key_mgmt=WPA-EAP
rsn_pairwise=CCMP
ieee80211w=1
auth_server_addr=192.0.2.10
auth_server_port=1812
auth_server_shared_secret=<shared secret>
dynamic_vlan=2
vlan_tagged_interface=trunk0
vlan_bridge=brvlan

dynamic_vlan=2 betekent “verplicht”: een aanmelding zonder VLAN in het antwoord wordt geweigerd in plaats van op een standaard-VLAN gedropt. Elk station meldde zich aan als anonymous over TLS 1.3, en het eigen log van het AP laat zien wat RADIUS het vertelde:

wlan0: STA 02:00:00:00:01:00 RADIUS: VLAN ID 10
wlan0: STA 02:00:00:00:02:00 RADIUS: VLAN ID 99
wlan0: STA 02:00:00:00:03:00 RADIUS: VLAN ID 30
WPA: group state machine entering state GTK_INIT (VLAN-ID 10)
WPA: group state machine entering state GTK_INIT (VLAN-ID 30)
WPA: group state machine entering state GTK_INIT (VLAN-ID 99)
Eén SSID, drie VLAN'slaptop1OU=staffcam1op de lijsttv1OU=mediaAccess pointSSID "Home"VLAN 10, sleutel AVLAN 99, sleutel BVLAN 30, sleutel CSwitchtrunkpoort10, 30, 99 getagdéén kabel
Eén SSID, drie apparaten, drie VLAN’s. Het AP zet elke client op een eigen VLAN-interface met een eigen groepssleutel, en stuurt alle drie over één getagde trunk. De switchpoort die het AP voedt moet elk VLAN dragen dat RADIUS kan noemen.
StationCertificaatVLANBridgeEigen gatewayHet VLAN van de tv
laptop1OU=staff10brvlan10BereikbaarGeen antwoord
cam1OU=iot, op de lijst99brvlan99Bereikbaar
tv1OU=media30brvlan30Bereikbaar

De regels over groepssleutels zijn het deel dat mensen missen. Broadcast- en multicastframes op WiFi worden versleuteld met een groepssleutel die iedereen op het netwerk deelt, en als alle drie de VLAN’s er één deelden, kon de camera de broadcasts van de laptop uit de lucht lezen, wat de switch ook dacht. hostapd houdt per VLAN een aparte groepssleutel bij, en het log laat zien dat hij er voor elk een start. Dezelfde run in WPA3-Enterprise, WPA-EAP-SHA256 met verplichte bescherming van managementframes, gaf dezelfde drie VLAN’s en dezelfde drie groepssleutels.

InstellingNetgear WAX en WBETP-Link Omada EAP
BeveiligingWPA2 Enterprise of WPA3 EnterpriseWPA2-Enterprise of WPA3-Enterprise, inclusief een only-modus met CCMP-128
VLAN van RADIUSDynamic VLAN, Enable23“Enable VLAN Assignment for Wireless Network”28
Geen VLAN in het antwoordClient gaat op het eigen VLAN van de SSID23Niet gedocumenteerd; FreeRADIUS stuurt hier nooit een Accept zonder
RADIUS-transportAlleen UDP, dus radsecproxyRadSec vanaf Controller 6.1 met actuele firmware20

Twee details van Netgear zijn goed om te weten voordat ze je bijten. Een client die van de RADIUS-server geen VLAN krijgt, wordt “automatically assigned the VLAN ID that you configured for the SSID”23, dus zet het eigen VLAN van de SSID op quarantaine, niet op personeel; FreeRADIUS stuurt er hier altijd een, maar de terugval hoort hoe dan ook dicht te falen. En dynamic VLAN “is mutually exclusive with a captive portal, NAT mode, wireless client isolation, and a multicast DNS (mDNS) gateway”23, dus een gastenportaal op dezelfde SSID kan niet.

WPA3-Enterprise op de WAX620 is de 192-bitsmodus: “the encryption is automatically set to GCMP256”32, met verplichte bescherming van managementframes. Clients die geen GCMP-256 kunnen, komen er niet op. Test je oudste apparaat voordat je overstapt, net als bij TLS 1.3.

De apparaten, en de controle die iedereen overslaat

Elk apparaat heeft drie dingen nodig: zijn eigen certificaat en sleutel, het rootcertificaat, en de naam van de RADIUS-server die het mag verwachten. De meeste handleidingen behandelen de eerste twee. Laat bij de derde iets weg en het werkt nog steeds, en dat is het probleem.

De derde is wat voorkomt dat een apparaat de verkeerde server vertrouwt. Hier werd twee keer een nep-RADIUS-server opgezet: één keer met een volkomen geldig certificaat van dezelfde CA, uitgegeven aan ha.home.example, en één keer met een zelfondertekend certificaat. Een laptop werd naar elk ervan gewezen met drie verschillende clientinstellingen:

Client controleertEchte serverZelfde CA, andere naamZelfondertekend
De CA en de servernaamVerbindtGeweigerd, Domain mismatchGeweigerd
Alleen de CAVerbindtVerbindtGeweigerd
NietsVerbindtVerbindtVerbindt

Kijk naar de middelste rij. Je CA ondertekent een certificaat voor elke webserver in huis. Een apparaat dat alleen de CA controleert, vertrouwt elk daarvan als zijn RADIUS-server. Wie één van die sleutels te pakken krijgt, van een vergeten testmachine of een webserver die niemand heeft gepatcht, kan dus een access point opzetten met jouw SSID, en je apparaten maken er zonder morren verbinding mee. Met EAP-TLS geven ze geen wachtwoord af, maar ze zitten wel op het netwerk van de aanvaller en vertrouwen het. TLS 1.3 verandert hier niets aan; dezelfde drie rijen kwamen terug onder 1.3. Alleen de naamcontrole doet dat.

Android was er het eerst bij. De optie “Do not validate” is verwijderd “for security reasons”33, al merkt het op dat “saved enterprise configurations that are set up to turn off server certificate validation aren’t affected”33. Een oude telefoon kan er dus nog een bij zich dragen.

Linux (NetworkManager)macOS, iOS, iPadOSAndroid
Certificaat en sleutel802-1x.client-cert, 802-1x.private-keyIdentity-payload in een configuratieprofielGeïnstalleerd als Wi-Fi-certificaat
Rootcertificaat802-1x.ca-certCertificate-payload, vertrouwd via PayloadCertificateAnchorUUID34Veld voor het CA-certificaat
Servernaam802-1x.domain-match35TLSTrustedServerNames34Domeinveld; setDomainSuffixMatch in de API36
TLS 1.3802-1x.phase1-auth-flags tls-1-3-enableTLSMinimumVersion en TLSMaximumVersion "1.3"; het maximum staat standaard op "1.2"34setMinimumTlsVersion(TLS_V1_3), vanaf API-level 34, waar het apparaat het ondersteunt36

Op Linux is het geheel één commando. Het is gecontroleerd met nmcli --offline, dat de verbinding afdrukt die het zou maken zonder het draaiende systeem aan te raken:

nmcli connection add type wifi con-name Home ifname '*' ssid Home \
  wifi-sec.key-mgmt wpa-eap 802-1x.eap tls 802-1x.identity anonymous \
  802-1x.ca-cert /etc/pki/home/root_ca.crt \
  802-1x.domain-match radius.home.example \
  802-1x.client-cert /etc/pki/home/laptop1.crt \
  802-1x.private-key /etc/pki/home/laptop1.key \
  802-1x.private-key-password-flags not-required \
  802-1x.phase1-auth-flags tls-1-3-enable

Voor een bekabelde poort is het hetzelfde met type ethernet en zonder SSID. Dat Apples maximale TLS-versie standaard op 1.2 staat is aan die kant de valkuil: een profiel dat het weglaat levert een Mac op die dit netwerk weigert.

Wat alleen TLS 1.3 kost

De eerlijke grens van deze opzet zijn de apparaten die het niet kunnen. Een camera of een tv met een ingebouwde supplicant van jaren terug kan misschien EAP-TLS over 1.2 en nooit over 1.3, en er is aan jouw kant geen instelling die dat oplost. De TLS-versie wordt afgesproken voordat FreeRADIUS het certificaat heeft gezien, dus hij kan geen uitzondering maken voor de IoT-rol.

Test dus elk apparaat voordat je het koopt. Niet erna. Wijs het naar een FreeRADIUS die alleen 1.3 spreekt en lees het log: recv TLS 1.3 Handshake, ClientHello of protocol version vertelt je alles wat je moet weten. Een apparaat dat faalt gaat op een bekabelde poort met MAB, of gaat niet op het netwerk. Het hele netwerk verzwakken om het slechtste ding erop tevreden te houden, is precies hoe het gedeelde wachtwoord begon.

De complete configuratie, bestand voor bestand

Alles hierboven is ingekort tot de regels die elk punt maken. Dit is het geheel. Elk bestand hier draaide in het lab, van een lege step-ca en een standaard FreeRADIUS-boom tot een laptop op VLAN 10, en de kopieën hieronder zijn diezelfde bestanden met de adressen van het lab vervangen door die in de tabel. Neem ze als één download, eap-tls-configs.tar.gz, of bestand voor bestand via de links.

NaamAdresWat het is
ca.home.exampleAlleen op naamstep-ca, poort 9000
radius.home.example192.0.2.10FreeRADIUS, RadSec op 2083
radsecproxy192.0.2.11radsecproxy, UDP erin, RadSec eruit
switch1, ap1192.0.2.21, 192.0.2.31Apparatuur die RadSec spreekt
Apparatuur met alleen UDP192.0.2.32 tot .63Via radsecproxy; het Linux-AP hieronder is ap2 op .33
BestandHoort inWat het doet
step-ca/templates/device.tpl/home/step/templates/ in de CARol in de OU, alleen clientauthenticatie
step-ca/templates/nas.tpl/home/step/templates/ in de CAOU=nas, voor RadSec
step-ca/setup.shEén keer draaien in de CAVoegt de provisioners network en nas toe
step-ca/issue.shDraaien waar step de CA vertrouwtElk certificaat, en het intrekkingsrecept
freeradius/install.shDraaien vanuit freeradius/Zet de rest op zijn plek
freeradius/mods-available/eapRADDB/mods-available/EAP-TLS, TLS 1.3, CRL gecontroleerd
freeradius/mods-available/device_vlanRADDB/mods-available/Leest de lijst per apparaat
freeradius/mods-available/mab_vlanRADDB/mods-available/Leest de MAB-lijst
freeradius/mods-config/device_vlan/devicesRADDB/mods-config/device_vlan/Certificaatnaam naar VLAN
freeradius/mods-config/device_vlan/mabRADDB/mods-config/device_vlan/MAC-adres naar VLAN
freeradius/policy.d/vlanRADDB/policy.d/Kiest het VLAN voor een certificaat
freeradius/policy.d/mabRADDB/policy.d/Kiest het VLAN voor een MAC-adres, alleen 20 of 99
freeradius/sites-available/default.patchToegepast op RADDBRoept de twee policy’s aan
freeradius/sites-available/tlsRADDB/sites-available/RadSec-listener en zijn clients
freeradius/nas-only.shRADDB/Laat alleen OU=nas-certificaten toe
freeradius/clients.confRADDB/UDP om te testen op de machine zelf
systemd/radius-crl-refresh/usr/local/sbin/Haalt de CRL op en controleert hem
systemd/radius-crl-refresh.service/etc/systemd/system/Draait dat
systemd/radius-crl-refresh.timer/etc/systemd/system/Elk uur
systemd/radius-cert-renew.service/etc/systemd/system/Vernieuwt het servercertificaat
radsecproxy/radsecproxy.conf/etc/radsecproxy.confUDP erin, RadSec eruit
hostapd/hostapd-wired.conf/etc/hostapd/Een Linux-switchpoort met 802.1X
hostapd/hostapd-wifi.conf/etc/hostapd/Een Linux-access point, één SSID
clients/wpa_supplicant-wired.confHet apparaatBekabeld inloggen
clients/wpa_supplicant-wifi.confHet apparaatInloggen via WiFi
clients/nmcli.shHet apparaatHetzelfde, via NetworkManager

FreeRADIUS staat op elke distributie op een andere plek, en de bestanden maakt het niet uit welke, want elk pad erin is opgebouwd vanuit de configuratiemap. install.sh neemt die map als RADDB. Het is gedraaid op alle drie hieronder, en de eigen configuratiecontrole van FreeRADIUS, -XC, slaagde op elk ervan. Alleen het officiële image heeft daarna elke test in dit bericht gedraaid; op de andere twee is die controle het hele bewijs, in een container, dus SELinux op een echte Fedora-machine hoorde er niet bij.

Officieel imageDebian 13Fedora
FreeRADIUS3.2.103.2.73.2.8
RADDB/etc/freeradius/etc/freeradius/3.0/etc/raddb
Draait alsfreeradfreeradradiusd
ServiceDe containerfreeradiusradiusd
GetestAlles in dit berichtinstall.sh en -XCinstall.sh en -XC

De twee systemd-units noemen /etc/freeradius en de service freeradius. Op Debian of Fedora pas je beide aan volgens de tabel. Verder verandert er niets.

step-ca

Het apparaatsjabloon is dat uit De rol in het certificaat zetten, regel voor regel, met een commentaar erboven. Het nas-sjabloon is hetzelfde idee met de rol vast:

step-ca/templates/nas.tpl

{{- /* Certificates for switches, access points and radsecproxy, used
       for RadSec. OU=nas is what FreeRADIUS checks for; it maps to no
       VLAN, so a nas certificate cannot get a device on the network. */ -}}
{
  "subject": {
    "commonName": {{ toJson .Subject.CommonName }},
    "organizationalUnit": "nas"
  },
  "sans": {{ toJson .SANs }},
  "keyUsage": ["digitalSignature"],
  "extKeyUsage": ["clientAuth"],
  "crlDistributionPoints": ["http://ca.home.example/1.0/crl"]
}

setup.sh voegt beide provisioners toe. Draai het één keer in de step-ca-container. Het vraagt voor elk een wachtwoord, en het CRL-blok dat het afdrukt zet je met de hand in ca.json:

step-ca/setup.sh

#!/bin/sh
# Run inside the step-ca container, as the step user, once.
# Adds the two provisioners this post uses. Each asks for a password for
# its own key; keep both.
set -e

step ca provisioner add network --type JWK --create \
  --x509-max-dur 8760h --x509-default-dur 24h \
  --x509-template /home/step/templates/device.tpl

step ca provisioner add nas --type JWK --create \
  --x509-max-dur 8760h --x509-default-dur 24h \
  --x509-template /home/step/templates/nas.tpl

# Then add this block at the top level of /home/step/config/ca.json and
# restart the container:
#
#   "crl": {
#     "enabled": true,
#     "generateOnRevoke": true,
#     "cacheDuration": "24h",
#     "idpURL": "http://ca.home.example/1.0/crl"
#   }

issue.sh maakt elk certificaat in dit bericht, en bewaart het intrekkingsrecept uit het hoofdstuk over intrekking als commentaar voor de dag dat je het nodig hebt. Dat recept is ook tegen deze CA gedraaid, op een laptop2 die daarvoor gemaakt is:

step-ca/issue.sh

#!/bin/sh
# Issue the certificates. Run anywhere the step CLI trusts the CA.
set -e

# The RADIUS server: default 24 hours, renewed daily by radius-cert-renew.service.
step ca certificate radius.home.example radius.crt radius.key --provisioner admin

# Devices: a year each, the role decides the VLAN.
step ca certificate laptop1 laptop1.crt laptop1.key --provisioner network --not-after 8760h --set role=staff
step ca certificate cam1    cam1.crt    cam1.key    --provisioner network --not-after 8760h --set role=iot
step ca certificate tv1     tv1.crt     tv1.key     --provisioner network --not-after 8760h --set role=media

# Switches, APs and radsecproxy, for RadSec.
step ca certificate switch1     switch1.crt     switch1.key     --provisioner nas --not-after 8760h
step ca certificate ap1         ap1.crt         ap1.key         --provisioner nas --not-after 8760h
step ca certificate radsecproxy radsecproxy.crt radsecproxy.key --provisioner nas --not-after 8760h

# A lost device: revoke by serial, no key needed.
#   SERIAL=$(step certificate inspect laptop2.crt --format json | jq -r .serial_number)
#   TOKEN=$(step ca token "$SERIAL" --revoke --provisioner admin)
#   step ca revoke "$SERIAL" --token "$TOKEN" --reason "laptop lost"

FreeRADIUS

Draai install.sh vanuit de map freeradius van de download. Het kopieert de bestanden, past de standaardsite aan en maakt de links. Meer doet het niet:

freeradius/install.sh

#!/bin/sh
#  Puts these files into a stock FreeRADIUS 3.2 tree. Run from this
#  directory. RADDB is /etc/freeradius on the official image,
#  /etc/freeradius/3.0 on Debian and Ubuntu, /etc/raddb on Fedora.
set -eu
RADDB=${RADDB:-/etc/freeradius}

cp mods-available/eap mods-available/device_vlan mods-available/mab_vlan "$RADDB/mods-available/"
mkdir -p "$RADDB/mods-config/device_vlan"
cp mods-config/device_vlan/devices mods-config/device_vlan/mab "$RADDB/mods-config/device_vlan/"
cp policy.d/vlan policy.d/mab "$RADDB/policy.d/"
cp sites-available/tls "$RADDB/sites-available/"
cp clients.conf "$RADDB/"
install -m 0755 nas-only.sh "$RADDB/nas-only.sh"
patch -d "$RADDB" -p0 --forward < sites-available/default.patch

for m in eap device_vlan mab_vlan; do
	ln -sfn ../mods-available/$m "$RADDB/mods-enabled/$m"
done
ln -sfn ../sites-available/tls "$RADDB/sites-enabled/tls"

#  The certificates go in $RADDB/certs/eaptls: radius.crt, radius.key,
#  and ca/ holding root_ca.crt, intermediate_ca.crt and the CRL. All of
#  it readable by the user FreeRADIUS runs as: freerad, or radiusd on
#  Fedora.
mkdir -p "$RADDB/certs/eaptls/ca"

De patch is twee regels in de standaardsite. Hij is gemaakt tegen 3.2.10 en past op 3.2.7 en 3.2.8 met niets ergers dan een offset:

freeradius/sites-available/default.patch

--- sites-available/default
+++ sites-available/default
@@ -301,6 +301,9 @@
 #  Make *sure* that 'preprocess' comes before any realm if you
 #  need to setup hints for the remote radius server
 authorize {
+	#  MAC Authentication Bypass, policy.d/mab.
+	mab
+
 	#
 	#  Take a User-Name, and perform some checks on it, for spaces and other
 	#  invalid characters.  If the User-Name appears invalid, reject the
@@ -810,6 +813,9 @@
 #  Once we KNOW that the user has been authenticated, there are
 #  additional steps we can take.
 post-auth {
+	#  The VLAN for a certificate login, policy.d/vlan.
+	vlan_by_certificate
+
 	#
 	#  If you need to have a State attribute, you can
 	#  add it here.  e.g. for later CoA-Request with

mods-available/eap vervangt de standaardversie. Helemaal:

freeradius/mods-available/eap

#  EAP for 802.1X: EAP-TLS only, TLS 1.3 only, CRL checked on every login.
#  Replaces the stock mods-available/eap. mods-enabled/eap links to it.
eap {
	default_eap_type = tls
	timer_expire = 60
	ignore_unknown_eap_types = no
	max_sessions = ${max_requests}

	tls-config tls-common {
		#  The server certificate followed by step-ca's intermediate,
		#  exactly as "step ca certificate" writes it.
		certificate_file = ${certdir}/eaptls/radius.crt
		private_key_file = ${certdir}/eaptls/radius.key

		#  Root, intermediate and the intermediate's CRL, hashed with
		#  "openssl rehash". radius-crl-refresh keeps the CRL current.
		ca_path = ${certdir}/eaptls/ca
		ca_file = ${certdir}/eaptls/ca/root_ca.crt

		check_crl = yes
		#  A HUP does not reload a CRL. This does, at 300 s, the minimum.
		ca_path_reload_interval = 300

		tls_min_version = "1.3"
		tls_max_version = "1.3"
		cipher_list = "DEFAULT"
		ecdh_curve = ""

		cache {
			enable = no
		}

		verify {
		}
	}

	tls {
		tls = tls-common
	}
}

policy.d/vlan is de policy van eerder, met de if (&EAP-Message)-omhulling die het MAB-hoofdstuk noemt:

freeradius/policy.d/vlan

#  Called first in post-auth. Picks the VLAN for a certificate login:
#  the per-device list first, then the role in the certificate's OU.
vlan_by_certificate {
	#  Certificate logins only. MAB is decided in authorize.
	if (&EAP-Message) {
		#  Only certificates made for this job: client auth, and a role.
		if (&TLS-Client-Cert-X509v3-Extended-Key-Usage-OID !~ /1\.3\.6\.1\.5\.5\.7\.3\.2/) {
			reject
		}

		#  1. The per-device list wins.
		device_vlan

		#  2. Otherwise the role in the certificate decides.
		if (!&reply:Tunnel-Private-Group-Id) {
			if (&TLS-Client-Cert-Subject =~ /\/OU=([^\/]+)/) {
				switch "%{1}" {
					case "staff" {
						update reply { &Tunnel-Private-Group-Id := "10" }
					}
					case "iot" {
						update reply { &Tunnel-Private-Group-Id := "20" }
					}
					case "media" {
						update reply { &Tunnel-Private-Group-Id := "30" }
					}
					case {
						reject
					}
				}
			}
			else {
				reject
			}
		}

		update reply {
			&Tunnel-Type := VLAN
			&Tunnel-Medium-Type := IEEE-802
		}
	}
}

policy.d/mab is de policy uit MAB, voor de printer die dit allemaal niet kan, en mods-available/device_vlan en nas-only.sh zijn zoals eerder getoond, alle drie met een commentaar erbij. De module voor de MAB-lijst en de twee lijsten:

freeradius/mods-available/mab_vlan

#  MAC Authentication Bypass: MAC address to VLAN, for kit with no
#  802.1X client. The mab policy only accepts VLAN 20 or 99 from here.
#  mods-enabled/mab_vlan links to it.
passwd mab_vlan {
	filename = ${modconfdir}/device_vlan/mab
	format = "*User-Name:=Tunnel-Private-Group-Id"
	delimiter = ","
}

freeradius/mods-config/device_vlan/devices

# Certificate CN,VLAN. Overrides the role in the certificate.
# cam1 is quarantined while it is looked at.
cam1,99

freeradius/mods-config/device_vlan/mab

# MAC (12 hex digits, lowercase, no punctuation),VLAN. 20 or 99 only.
aabbccddeeff,20

sites-available/tls vervangt het standaardvoorbeeld voor RadSec. De tijdelijke map voor verify is ${run_dir}, die al bestaat en van de gebruiker is waaronder FreeRADIUS draait, dus er hoeft niets aangemaakt te worden:

freeradius/sites-available/tls

#  RadSec: RADIUS over TLS 1.3 on port 2083, for switches, access
#  points and radsecproxy. Replaces the stock sites-available/tls;
#  sites-enabled/tls links to it. Requests go to the default site.
listen {
	ipaddr = *
	port = 2083
	type = auth
	proto = tcp
	virtual_server = default
	clients = radsec

	limit {
		max_connections = 16
		lifetime = 0
		idle_timeout = 30
	}

	tls {
		certificate_file = ${certdir}/eaptls/radius.crt
		private_key_file = ${certdir}/eaptls/radius.key
		ca_path = ${certdir}/eaptls/ca
		ca_file = ${certdir}/eaptls/ca/root_ca.crt

		require_client_cert = yes
		check_crl = yes
		ca_path_reload_interval = 300

		#  Only certificates from step-ca's nas provisioner.
		verify {
			#  Where FreeRADIUS writes the certificate for the script to read.
			tmpdir = ${run_dir}
			client = "${raddbdir}/nas-only.sh %{TLS-Client-Cert-Filename}"
		}

		tls_min_version = "1.3"
		tls_max_version = "1.3"
		cipher_list = "DEFAULT"
		ecdh_curve = ""

		cache {
			enable = no
		}
	}
}

#  One entry per switch, AP and proxy, by management address. Over TLS
#  the secret does nothing, and FreeRADIUS requires it to be "radsec".
clients radsec {
	client switch1 {
		ipaddr = 192.0.2.21
		proto = tls
		secret = radsec
	}
	client ap1 {
		ipaddr = 192.0.2.31
		proto = tls
		secret = radsec
	}
	client radsecproxy {
		ipaddr = 192.0.2.11
		proto = tls
		secret = radsec
	}
}

clients.conf vervangt de standaardversie en laat eapol_test op de machine zelf toe en verder niets. Switches en AP’s komen binnen via RadSec of via de proxy:

freeradius/clients.conf

#  UDP clients. Kit talks RadSec (sites-available/tls) or goes through
#  radsecproxy, so plain RADIUS is only for testing on this host with
#  eapol_test. Replaces the stock clients.conf.
client localhost {
	ipaddr = 127.0.0.1
	proto = *
	secret = CHANGE-ME-test-only
	require_message_authenticator = yes
	nas_type = other
}

Controleer eerst de configuratie. Start het dan op de voorgrond, met threads aan zodat RadSec werkt:

freeradius -XC
freeradius -fxx -l stdout

Actueel houden

radius-crl-refresh haalt de CRL op met de root vastgepind, en weigert er een die niet door de intermediate is ondertekend of al verlopen is, zodat een mislukte download de oude lijst laat staan. Het ruilt nooit een goede lijst in voor rommel:

systemd/radius-crl-refresh

#!/bin/sh
# Fetch step-ca's CRL over HTTPS with the root pinned, and put it where
# FreeRADIUS reads it. FreeRADIUS picks it up within ca_path_reload_interval.
# Installed as /usr/local/sbin/radius-crl-refresh.
set -eu

CA_URL="${CA_URL:-https://ca.home.example:9000}"
DIR="${DIR:-/etc/freeradius/certs/eaptls/ca}"
TMP=$(mktemp -d)
trap 'rm -rf "$TMP"' EXIT

curl -fsS --cacert "$DIR/root_ca.crt" --tlsv1.3 -o "$TMP/crl.der" "$CA_URL/1.0/crl"
openssl crl -inform DER -in "$TMP/crl.der" -out "$TMP/crl.pem"

# Refuse a list that is not signed by our intermediate, or already stale.
openssl crl -in "$TMP/crl.pem" -noout -CAfile "$DIR/intermediate_ca.crt" 2>&1 | grep -q 'verify OK'
next=$(date -d "$(openssl crl -in "$TMP/crl.pem" -noout -nextupdate | cut -d= -f2)" +%s)
[ "$next" -gt "$(date +%s)" ]

install -m 0644 "$TMP/crl.pem" "$DIR/intermediate.crl.pem.new"
mv -f "$DIR/intermediate.crl.pem.new" "$DIR/intermediate.crl.pem"
openssl rehash "$DIR"

systemd/radius-crl-refresh.service

[Unit]
Description=Fetch step-ca's CRL for FreeRADIUS
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/radius-crl-refresh

systemd/radius-crl-refresh.timer

[Unit]
Description=Fetch step-ca's CRL for FreeRADIUS every hour

[Timer]
OnBootSec=1min
OnUnitActiveSec=1h
RandomizedDelaySec=5min
Persistent=true

[Install]
WantedBy=timers.target

Het servercertificaat leeft 24 uur, dus het vernieuwt zichzelf. Bij vernieuwen bleven hier de sleutel en de rechten van beide bestanden behouden, en dat doet ertoe omdat FreeRADIUS ze leest als freerad. Het herstart FreeRADIUS in plaats van een HUP te sturen. Zelfde reden als bij de CRL:

systemd/radius-cert-renew.service

#  Keeps FreeRADIUS's 24-hour server certificate renewed, and restarts
#  FreeRADIUS on each renewal so it loads the new one. Once, as root:
#    step ca bootstrap --ca-url https://ca.home.example:9000 --fingerprint <root fingerprint>
#    chgrp freerad radius.key && chmod 0640 radius.key
#  Renewal rewrites radius.crt and keeps the key and both files' modes.
[Unit]
Description=Renew FreeRADIUS's server certificate from step-ca
Wants=network-online.target
After=network-online.target

[Service]
ExecStart=/usr/bin/step ca renew --daemon \
  --exec "systemctl restart freeradius" \
  /etc/freeradius/certs/eaptls/radius.crt /etc/freeradius/certs/eaptls/radius.key
Restart=on-failure
RestartSec=5min

[Install]
WantedBy=multi-user.target

radsecproxy

radsecproxy/radsecproxy.conf

# radsecproxy beside switches and APs that only speak UDP RADIUS.
# Takes UDP in on the management VLAN, sends RadSec over TLS 1.3 out.

ListenUDP *:1812

LogLevel 3
LogDestination file:///var/log/radsecproxy.log

tls stepca {
    CACertificateFile  /etc/radsecproxy/root_ca.crt
    CertificateFile    /etc/radsecproxy/radsecproxy.crt
    CertificateKeyFile /etc/radsecproxy/radsecproxy.key
    TlsVersion TLS1_3
}

# The kit that only speaks UDP, from 192.0.2.32 to .63 on the management
# VLAN. Netgear caps the secret at 16 alphanumeric characters, so use all
# 16, random:
#   openssl rand -base64 32 | tr -dc 'A-Za-z0-9' | head -c 16
client switches {
    host 192.0.2.32/27
    type udp
    secret CHANGE-ME-16chars
    RequireMessageAuthenticator on
}

server freeradius {
    host radius.home.example
    port 2083
    type tls
    tls stepca
    secret radsec
    certificateNameCheck on
}

realm * {
    server freeradius
}

hostapd

Een Linux-machine die voor een switchpoort speelt en RadSec spreekt. Dit is het bestand waarmee de gesimuleerde poort draaide:

hostapd/hostapd-wired.conf

# A Linux box as an 802.1X wired port, talking RadSec to FreeRADIUS.
# Needs hostapd built with CONFIG_DRIVER_WIRED=y and CONFIG_RADIUS_TLS=y.
interface=eth1
driver=wired
ieee8021x=1
use_pae_group_addr=1
eap_reauth_period=3600
nas_identifier=switch1

auth_server_addr=192.0.2.10
auth_server_port=2083
auth_server_type=TLS
auth_server_shared_secret=radsec
auth_server_ca_cert=/etc/hostapd/root_ca.crt
auth_server_client_cert=/etc/hostapd/switch1.crt
auth_server_private_key=/etc/hostapd/switch1.key

logger_syslog=-1
logger_syslog_level=2

En voor een access point. Dit gaat via radsecproxy over UDP, zoals een Netgear-AP zou doen, en is het bestand waarmee de gesimuleerde radio’s draaiden:

hostapd/hostapd-wifi.conf

# A Linux access point: one WPA2/WPA3-Enterprise SSID, each client on the
# VLAN RADIUS names. This one talks UDP to radsecproxy, like the Netgear
# APs. For RadSec, build hostapd with CONFIG_RADIUS_TLS=y and use the
# auth_server_* block from hostapd-wired.conf instead.
interface=wlan0
driver=nl80211
ssid=Home
country_code=GB
hw_mode=g
channel=6

ieee8021x=1
eapol_version=2
wpa=2
# WPA-EAP for WPA2-Enterprise; WPA-EAP-SHA256 with ieee80211w=2 for WPA3-Enterprise.
wpa_key_mgmt=WPA-EAP WPA-EAP-SHA256
rsn_pairwise=CCMP
ieee80211w=1
nas_identifier=ap2
own_ip_addr=192.0.2.33

auth_server_addr=192.0.2.11
auth_server_port=1812
auth_server_shared_secret=CHANGE-ME-16chars
radius_require_message_authenticator=1

# 2 = required: no VLAN in the answer means no network.
dynamic_vlan=2
vlan_naming=1
vlan_tagged_interface=eth0
vlan_bridge=brvlan

ctrl_interface=/run/hostapd
logger_syslog=-1
logger_syslog_level=2

De apparaten

nmcli.sh is het commando uit De apparaten, en de controle die iedereen overslaat plus zijn bekabelde tweelingbroer. Zonder NetworkManager neemt wpa_supplicant deze:

clients/wpa_supplicant-wired.conf

# Linux client, wired 802.1X. Run: wpa_supplicant -D wired -i eth0 -c this-file
ctrl_interface=/run/wpa_supplicant
ap_scan=0
network={
    key_mgmt=IEEE8021X
    eapol_flags=0
    eap=TLS
    phase1="tls_disable_tlsv1_3=0"
    identity="anonymous"
    ca_cert="/etc/pki/home/root_ca.crt"
    domain_match="radius.home.example"
    client_cert="/etc/pki/home/laptop1.crt"
    private_key="/etc/pki/home/laptop1.key"
}

clients/wpa_supplicant-wifi.conf

# Linux client, WiFi, without NetworkManager. TLS 1.3, anonymous identity,
# and the server name checked.
ctrl_interface=/run/wpa_supplicant
network={
    ssid="Home"
    key_mgmt=WPA-EAP WPA-EAP-SHA256
    ieee80211w=1
    eap=TLS
    phase1="tls_disable_tlsv1_3=0"
    identity="anonymous"
    ca_cert="/etc/pki/home/root_ca.crt"
    domain_match="radius.home.example"
    client_cert="/etc/pki/home/laptop1.crt"
    private_key="/etc/pki/home/laptop1.key"
}

Waartegen elk bestand getest is

BestandenGedraaid tegenResultaat
step-ca/step-ca 0.30.2, een nieuwe CAAlle 7 certificaten uitgegeven met hun rollen; laptop2 uitgegeven en ingetrokken met het recept
freeradius/3.2.10, geïnstalleerd met install.shlaptop1 VLAN 10, cam1 99, tv1 30, een nas-certificaat geweigerd; MAB aabbccddeeff VLAN 20, een onbekend MAC-adres geweigerd
radius-crl-refreshstep-ca over TLS 1.3CRL geïnstalleerd en gehasht; de ingetrokken laptop binnen vijf minuten geweigerd, zonder herstart
radius-cert-renew.servicestep ca renew 0.30.2Certificaat vernieuwd; sleutel en rechten onaangeroerd
radsecproxy.confradsecproxy 1.11.2EAP-TLS erdoorheen geaccepteerd; MAB met Message-Authenticator VLAN 20, zonder weggegooid
hostapd-wired.confhostapd 2.11, gebouwd met RadSecVLAN’s 10, 99 en 30 over TLS 1.3
hostapd-wifi.conf, wpa_supplicant-wifi.confGesimuleerde radio’s, via radsecproxyDrie stations op één SSID, VLAN’s 10, 99 en 30; het clientbestand zoals geschreven en een station met alleen WPA3 kwamen allebei binnen
wpa_supplicant-wired.confeapol_test 2.11Dezelfde antwoorden als de tabel hogerop
systemd-unitssystemd-analyze verifyGeen klacht behalve dat de twee programma’s hier niet geïnstalleerd zijn

Wat iedereen buitensluit

Elk van deze is tijdens het bouwen tegengekomen of getest, behalve het ontbrekende VLAN op een trunk, waarvoor je een echte switch nodig hebt om het te laten zien. Bijna allemaal falen ze dicht. Dat is de juiste manier om te falen, en toch een slechte ochtend.

FoutWat je zietOplossing
CRL verlopen of ontbrekendElk apparaat geweigerd; CRL has expired of unable to get certificate CRLHaal de CRL op met een timer en bewaak die
Nieuwe CRL niet opgepiktEen ingetrokken apparaat komt er nog opca_path_reload_interval = 300, of herstart FreeRADIUS
Client heeft TLS 1.3 uitprotocol version, apparaat geweigerdtls_disable_tlsv1_3=0, tls-1-3-enable of de sleutels in het Apple-profiel
Certificaatbestanden niet leesbaar voor freeradFreeRADIUS start niet, Permission deniedMaak ze leesbaar voor freerad, de sleutel alleen voor die gebruiker
Certificaat zonder rolGeweigerd door de policyOpnieuw uitgeven via de network-provisioner met --set role=
VLAN niet op de switch of op de trunk van het APApparaat meldt zich aan, krijgt geen adresMaak het VLAN aan en tag het op elke trunk die het apparaat kan bereiken
Verkeerd gedeeld RADIUS-geheimGeen antwoord; FreeRADIUS logt invalid Message-Authenticator! (Shared secret is incorrect.) en de switch probeert opnieuw tot hij opgeeftHetzelfde geheim in clients.conf en op de switch of het AP
Apparaat controleert de servernaam nietNiets. Het werkt, en het zou ook met een bedrieger werkenStel op elk apparaat de servernaam in
Verzoek zonder Message-AuthenticatorStil weggegooid; de switch probeert opnieuw en geeft opZet het aan op de switch, of zet radsecproxy ervoor
RadSec-listener onder freeradius -XThreading must be enabled for TLS socketsfreeradius -fxx -l stdout
hostapd gebouwd zonder RadSecunsupported RADIUS type 'TLS'Bouw met CONFIG_RADIUS_TLS=y, of gebruik radsecproxy
radsecproxy krijgt het adres van de serverNo subjectaltname matching addressGebruik radius.home.example

Waarom staat dit niet standaard op elk bedrijfsnetwerk?

Niets in dit bericht is nieuw. EAP-TLS werd gepubliceerd in oktober 199937. De VLAN-attributen volgden in september 20033. Beide switchfabrikanten hierboven documenteren dynamische VLAN’s in hun eigen handleidingen, en de CA, de RADIUS-server en de proxy zijn open source en kosten niets. Kies je de commerciële route, dan wordt hetzelfde een post op de begroting: Cisco zet “RADIUS authentication, authorization, and accounting, including 802.1X, MAC authentication bypass” in de Essentials-laag van ISE, gelicentieerd per “concurrent active endpoints”, waarbij een endpoint een van de “printers, fax machines, or other types of network devices”38 op het netwerk kan zijn. Hoe dan ook, de mogelijkheid zit al meer dan twintig jaar in de apparatuur.

Waarom draait een bedrijfsnetwerk in 2026 dan nog steeds op een wachtwoord dat iedereen deelt?

Mag ik vragen waarom zoveel beheerders en MSP’s simpelweg niet de moeite nemen? Niet wie. Wat het laat voortduren. Er zijn drie eerlijke antwoorden, omdat er drie gevallen zijn.

Sommigen weten het niet. En dat is het falen, niet het excuus. Weten wat je eigen switch kan is niet iets wat je overkomt. Zorgen dat je het weet is het werk, en de handleiding die het zegt is een gratis download.

Sommigen weten het, en laten het zo. Het gedeelde wachtwoord werkt. Niemand maakt er een ticket over aan. Een certificaat per apparaat is een stel klusjes waar niets tegenover staat tot de dag dat er een laptop verdwijnt, en als zodanig wint het nooit een plek op de lijst. Voor een MSP is de rekensom nog eenvoudiger: een plat netwerk met één wachtwoord is goedkoper te ondersteunen, geen contract meet het, en de klant vraagt er nooit naar. De MSP-reeks loopt na waar die prikkels toe leiden, en dit is hetzelfde patroon op een kleinere rekening. Dit is het geval waar de vraag op gericht is: de beheerder die de switch heeft die het kan, tot hier heeft gelezen, en de gedeelde sleutel houdt omdat niemand hem dwingt het te verantwoorden.

Sommigen kunnen het echt nog niet. De printer zonder supplicant, de tv die geen TLS 1.3 doet, de bedrijfsapplicatiekast die de leverancier niet wil bijwerken. Dat is een echte beperking en het antwoord is ja, dat is te verdedigen: MAB op een afgeschermd VLAN, de reden opgeschreven, en een datum ernaast voor wanneer het apparaat verdwijnt.

Het falen is niet dat er een gat is. Iedereen heeft gaten. Het is een bedrijfsnetwerk laten draaien over een gat dat je nooit bent gaan dichten, op apparatuur die werd geleverd met de mogelijkheid om het te dichten.

Iedereen heeft ooit een gedeeld wachtwoord gebruikt. Er bewust een houden op een bedrijfsnetwerk is het deel dat niet te verdedigen is.

Waarom Cyber Essentials er niet om vraagt

Omdat het daar nooit voor bedoeld was. Cyber Essentials is de door de overheid gesteunde basislijn, en “a growing number of organisations require suppliers to be certified to bid for work”39. Het is opgezet tegen “the most common internet based cyber security threats”39, en de eisen zeggen expliciet waar het ophoudt. Draadloze apparatuur valt buiten de reikwijdte waar een aanvaller er vanaf het internet niet bij kan, omdat “the Cyber Essentials scheme isn’t concerned with attacks that can only be launched within the signal range of the wireless device”40. De vijf maatregelen zijn firewalls, veilige configuratie, beveiligingsupdates, toegangsbeheer voor gebruikers en bescherming tegen malware40. Geen ervan vraagt wie er überhaupt op het netwerk mag. Het dichtst bij komt “change any default or guessable account passwords”40, en een WiFi-sleutel die op de balie bij de receptie staat is geen account.

VLAN’s komen er één keer in voor. Een “sub-set” van een organisatie, een die “segregated from the rest of the organisation by a firewall or VLAN”40 is, kan apart worden beoordeeld. De enige plek waar het schema een VLAN tegenkomt is dus als manier om de grens om een kleiner deel van het netwerk te trekken.

Op zijn eigen voorwaarden is dat redelijk. Het is een ondergrens tegen aanvallen vanaf het internet, en dat zegt het ook. Het probleem is wat er met een ondergrens gebeurt zodra er een certificaat van aan de muur hangt. Het wordt het doel, en daarna het plafond, en een bedrijf kan ervoor slagen met elk apparaat op één plat netwerk achter een wachtwoord dat de schoonmaker kent. Iedereen die met een laptop langs het gebouw loopt, zit binnen het dreigingsmodel dat ertoe doet en buiten het model dat werd beoordeeld.

Een wachtwoord is een belofte die niemand nakomt

Wat me opviel bij het bouwen hiervan, is hoe weinig ervan moeilijk was. Elke valkuil stond ergens opgeschreven, in een commentaarregel in een config of in een handleiding van een fabrikant, en elke ervan gaf zich gewonnen na een middag in een lab.

En nog steeds draait het huis op een WiFi-wachtwoord dat op de koelkast staat. Niet omdat het alternatief moeilijk is, maar omdat het wachtwoord de standaard is, en de standaard hoeft zich nooit te verantwoorden. Iedereen weet dat het gedeeld wordt. Iedereen weet dat het lekt. Niemand verandert het, want veranderen betekent met het nieuwe langs elk apparaat in huis lopen, dus het blijft jarenlang hetzelfde, door elke gast, elke huurder en elke telefoon die met het wachtwoord nog opgeslagen in een recyclebak verdween, en het laat iedereen binnen die het ooit heeft gehad.

Een certificaat per apparaat is een ander idee van waar het netwerk voor is. Het netwerk hoort te weten wat iets is, niet alleen dat iemand er ooit het juiste woord in heeft getypt. Dat is een standaard waar je jezelf aan houdt terwijl niemand controleert, en dat is de enige soort die iets waard is. De camera mag niet naast de laptop zitten omdat hij toevallig op dezelfde SSID zat. Hij zit waar jij hem zet, op apparatuur die van jou is, onder een regel die je zelf schreef en kunt lezen. Gaat hij zich misdragen, dan verhuist hij met één regel in een tekstbestand. Raakt hij kwijt, dan sluit één commando hem buiten.

De ruil is klusjes. Een CRL met een timer erachter, certificaten met een datum in de agenda, apparaten die getest worden voordat ze gekocht worden. Dat is de prijs, en die zit de goede kant op: je betaalt in moeite die je kunt zien, in plaats van in een wachtwoord dat al jaren stilletjes mensen binnenlaat.


  1. RFC 3748 — Extensible Authentication Protocol (EAP) — EAP en de rollen peer, authenticator en authenticatieserver waartussen het loopt. ↩︎

  2. RFC 2868 — RADIUS Attributes for Tunnel Protocol Support — definieert de tunnelattributen die RFC 3580 hergebruikt voor VLAN’s. ↩︎

  3. RFC 3580 — IEEE 802.1X RADIUS Usage Guidelines — sectie 3.31, “Tunnel-Type=VLAN (13) Tunnel-Medium-Type=802 Tunnel-Private-Group-ID=VLANID”, en “the VLANID integer value is encoded as a string”. ↩︎ ↩︎ ↩︎

  4. Microsoft Security Advisory 2743314 — “An attacker who obtained the MS-CHAP v2 authentication traffic could then use the exploit code to decrypt a user’s credentials.” ↩︎

  5. RFC 5216 — The EAP-TLS Authentication Protocol — wederzijdse authenticatie met certificaten over EAP. ↩︎

  6. RFC 9190 — EAP-TLS 1.3 — “TLS 1.3 also encrypts certificates and some of the extensions. When using EAP-TLS with TLS 1.3, the use of privacy is mandatory and does not cause any additional round trips”; sectie 2.1.7 raadt anonieme NAI’s aan in de identiteitsrespons. ↩︎ ↩︎

  7. FreeRADIUS 3.2.10 — mods-available/eap — check_crl, check_all_crl “Check if intermediate CAs have been revoked”, allow_expired_crl, en “most EAP supplicants WILL NOT DO TLS 1.3 PROPERLY”. ↩︎ ↩︎ ↩︎

  8. Smallstep — Certificate templates — gebruikersgegevens die met --set worden meegegeven zijn in het sjabloon beschikbaar als .Insecure.User; verpak waarden altijd in toJson. ↩︎

  9. FreeRADIUS 3.2.10 — dictionary.freeradius.internal — de TLS-Client-Cert-*-attributen die de policy leest, waaronder TLS-Client-Cert-Subject en TLS-Client-Cert-X509v3-Extended-Key-Usage-OID. ↩︎

  10. FreeRADIUS 3.2.10 — mods-available/passwd — “Attributes marked as ‘=’ are added to reply_items instead of default configure_items”. ↩︎

  11. wpa_supplicant — wpa_supplicant.conf — “tls_disable_tlsv1_3=0 - enable TLSv1.3 (experimental - disabled by default)”, en domain_match, de “Constraint for server domain name”. ↩︎

  12. RFC 2548 — Microsoft Vendor-specific RADIUS Attributes — MS-MPPE-Send-Key en MS-MPPE-Recv-Key, versleuteld als “b(1) = MD5(S + R + A) c(1) = p(1) xor b(1)”, waarbij S het gedeelde geheim is. ↩︎

  13. BlastRADIUS — CVE-2024-3596; de aanbevolen mitigatie is “to mandate that clients and servers always send and require Message-Authenticator attributes for all requests and responses”. ↩︎ ↩︎

  14. RFC 3579 — RADIUS Support For EAP — “the Message-Authenticator attribute MUST be used to protect all Access-Request, Access-Challenge, Access-Accept, and Access-Reject packets containing an EAP-Message attribute.” ↩︎

  15. FreeRADIUS 3.2.10 — radiusd.conf — require_message_authenticator en limit_proxy_state; beide op “no” zetten “will allow MITM attackers to create fake Access-Accept packets to the NAS!” ↩︎

  16. RFC 6614 — TLS Encryption for RADIUS — RadSec, mei 2012, Experimental. ↩︎

  17. IETF — RadSec: RADIUS over TLS and DTLS — beoogde status Proposed Standard, vervangt RFC 6614 en RFC 7360; IESG-status “Has enough positions to pass” bij controle op 13 oktober 2026. ↩︎

  18. FreeRADIUS 3.2.10 — sites-available/tls — de RadSec-listener, require_client_cert, proto = tls, en “TLS clients MUST have the shared secret set to ‘radsec’”. ↩︎

  19. hostapd 2.11 source — hostapd/defconfig: “Include support fo RADIUS/TLS into the RADIUS client”, #CONFIG_RADIUS_TLS=y. ↩︎

  20. TP-Link — Omada Software Controller 6.1 release note — “Added support for RadSec (RADIUS over TLS) to secure SSID, MAC-Based, and 802.1X authentications on both APs and Switches”, met de nieuwste firmware voor AP’s en switches als vereiste. ↩︎ ↩︎

  21. TP-Link Omada — 802.1X configuration — RADIUS Proxy: “It does not support the RadSec feature”; met RadSec “it is recommended to disable Require Message-Authenticator”. ↩︎

  22. Netgear — M4250 and M4350 CLI manual — radius server key: “The secret must be an alphanumeric value not exceeding 16 characters”; RadSec wordt niet genoemd. ↩︎

  23. Netgear — WAX630 user manual — “Enter the number of the UDP port on the access point that is used to access the RADIUS server”, en “By default, the password is sharedsecret.” ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  24. radsecproxy — “made to be able to deploy RadSec (RADIUS over TLS) so that all RADIUS communication across network links could be done using TLS, without modifying existing RADIUS software”. Hier getest met 1.11.2. ↩︎

  25. RFC 9765 — RADIUS/1.1 — april 2025, Experimental: “the RADIUS shared secret is no longer used, and all MD5-based packet authentication and attribute obfuscation methods are removed.” ↩︎

  26. FreeRADIUS 3.2.10 — ChangeLog — RADIUS/1.1 is “Disabled by default, can be enabled by passing --with-radiusv11 to the configure script. For now, this is for testing interoperability.” ↩︎

  27. Netgear — M4250 user manual — VLAN Assignment Mode, “The default is Disable”; Dynamic VLAN Creation Mode; Authentication Order, “if a client tries 802.1X authentication (based on the user and client credentials) and authentication fails, the interface proceeds with MAB authentication.” De handleiding van de M4350 heeft dezelfde tekst. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  28. TP-Link Omada — Dynamic VLAN with the VLAN Assignment function of RADIUS — switches: Authentication Type “Port Based”, “enable ‘VLAN Assignment’”, “make sure the 802.1X Control mode is Auto”; access points: “Enable VLAN Assignment for Wireless Network” in het RADIUS-profiel. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  29. TP-Link — Configuration guide for 802.1X VLAN assignment and MAB — “VLAN Assignment takes effect only when the control type is Port Based”; ontbrekende VLAN’s worden automatisch aangemaakt; MAB stuurt het MAC-adres als “12 hexadecimal digits, all lowercase and no punctuation”. ↩︎ ↩︎ ↩︎ ↩︎

  30. Netgear — GS710TUP user manual — MAC-based port control “allows multiple supplicants connected to the same port”. ↩︎

  31. hostapd — hostapd.conf — dynamische VLAN’s “parsed from following RADIUS attributes based on RFC 3580 and RFC 2868”; dynamic_vlan “2 = required; reject authentication if RADIUS server does not include VLAN ID”; vlan_tagged_interface en vlan_bridge. Gelezen uit het pakket van hostapd 2.11. ↩︎

  32. Netgear — WAX620 user manual — WPA3 Enterprise: “the encryption is automatically set to GCMP256”. ↩︎

  33. Pixel Phone Help — Connect to Wi-Fi networks — “The ‘Do not validate’ setting option used in EAP-PEAP, EAP-TLS and EAP-TTLS configurations has been removed for security reasons.” ↩︎ ↩︎

  34. Apple — WiFi.EAPClientConfiguration — TLSTrustedServerNames, “If a server presents a certificate that isn’t in this list, the system doesn’t trust it”; TLSMaximumVersion staat standaard op 1.2. ↩︎ ↩︎ ↩︎

  35. NetworkManager — 802-1x settings — domain-match: “Constraint for server domain name”, plus ca-cert, client-cert, private-key en phase1-auth-flags. ↩︎

  36. Android Developers — WifiEnterpriseConfig — setDomainSuffixMatch; TLS_V1_3 “Added in API level 34”, waarbij WifiManager.isTlsV13Supported() zegt “whether or not TLS v1.3 is supported”. ↩︎ ↩︎

  37. RFC 2716 — PPP EAP TLS Authentication Protocol — de oorspronkelijke EAP-TLS-specificatie, oktober 1999, sindsdien vervangen door RFC 5216. ↩︎

  38. Cisco — ISE licences — Essentials omvat “RADIUS authentication, authorization, and accounting, including 802.1X, MAC authentication bypass and easy connect”; “Concurrent active endpoints refer to the total number of supported users and devices… printers, fax machines, or other types of network devices.” ↩︎

  39. NCSC — About Cyber Essentials — “A growing number of organisations require suppliers to be certified to bid for work”; het beschermt tegen “the most common internet based cyber security threats”. ↩︎ ↩︎

  40. NCSC — Cyber Essentials: Requirements for IT Infrastructure v3.3 — april 2026. Draadloze apparaten vallen buiten de reikwijdte waar ze niet vanaf het internet aan te vallen zijn: “the Cyber Essentials scheme isn’t concerned with attacks that can only be launched within the signal range of the wireless device”; de vijf technische maatregelen; “change any default or guessable account passwords”; een sub-set “is part of the organisation whose network is segregated from the rest of the organisation by a firewall or VLAN”. ↩︎ ↩︎ ↩︎ ↩︎