Ein Passwort für das ganze Haus

Ein WLAN-Passwort teilt sich alles, was je beigetreten ist. Die Laptops, die Telefone, der Fernseher, die Türklingelkamera mit einer Firmware, die seit der Montage niemand aktualisiert hat, der Gast, der es an Weihnachten bekommen hat, und jedes Telefon, das dieser Gast seitdem besessen hat. Alle sitzen im selben Netz. Jedes davon kann mit jedem anderen reden.

Das auf die übliche Art aufzuteilen heißt mehr SSIDs. Eine für die Belegschaft, eine fürs IoT, eine für Gäste, jede mit eigenem Passwort und eigenem VLAN. Das funktioniert. Dann leakt ein Passwort, und du läufst mit einem neuen zu jedem Gerät in dieser SSID, und genau das ist die Arbeit, die nie erledigt wird.

Ein freier Kabelport ist schlimmer. Überhaupt kein Passwort. Einstecken, und du bist in dem VLAN, auf das der Port eingestellt war.

802.1X mit Zertifikaten dreht das um. Jedes Gerät trägt sein eigenes Zertifikat und beweist, was es ist, bevor ein einziges Paket durchkommt, und dann entscheidet das Netz, nicht das Gerät und nicht wer auch immer das Passwort verteilt hat, wohin es gehört. Eine SSID. Jeder Switch-Port gleich. Der Laptop landet im VLAN der Belegschaft, die Kamera im IoT-VLAN, und ein Gerät, dem du nie ein Zertifikat ausgestellt hast, landet nirgends.

Ein gemeinsames PasswortEine SSID pro VLAN802.1X, eine SSID
Wer hineinkommtJeder, der das Passwort hatJeder, der das Passwort dieser SSID hatNur ein Gerät mit einem Zertifikat, das du ausgestellt hast
Welches VLANAlle in einemDie SSID, der sie beigetreten sindWas der RADIUS-Server für dieses Gerät sagt
Ein verlorenes Gerät aussperrenDas Passwort überall ändernDas Passwort dieser SSID überall ändernEin Zertifikat widerrufen
KabelportsOffenOffenDieselbe Prüfung wie im WLAN
Ausgestrahlte SSIDs1Eine pro VLAN1

Genau das baut dieser Beitrag. step-ca stellt die Zertifikate aus und schreibt die Rolle jedes Geräts hinein, FreeRADIUS prüft jedes Zertifikat gegen die CA und ihre Sperrliste, liest die Rolle heraus und antwortet mit einem VLAN, und der Switch oder der Access Point setzt das Gerät dorthin, wo man es ihm sagt. Die Geräte sind von Netgear oder TP-Link und sprechen mit FreeRADIUS über TLS, wo sie können. Alles lief hier, bevor irgendetwas davon in die Nähe echter Hardware kam: die CA und der RADIUS-Server in Containern, der Switch-Port und das WLAN in Simulatoren.

Vier Teile, und nur einer entscheidet

802.1X nennt drei Rollen1. Der Supplicant ist das Gerät, das beitreten will. Der Authenticator ist der Switch-Port oder der Access Point. Der Authentication Server ist RADIUS. Nimm die CA dazu, die die Zertifikate ausgestellt hat, und das ist das ganze System.

Wer was bei einer 802.1X-Anmeldung tutGerätSupplicantSwitch oder APAuthenticatorreicht weiter, entscheidet nichtsFreeRADIUSwählt das VLANstep-caZertifikate und CRLEAP-TLSPort öffnetRadSecVLAN 10CRL, HTTPSZertifikat, einmal ausgestellt
Der Switch oder AP reicht nur weiter. EAP reist vom Gerät zu FreeRADIUS in EAPOL und dann in RADIUS, und die Antwort kommt mit dem VLAN in drei Attributen zurück. step-ca sieht nie eine Anmeldung; es hat die Zertifikate vorher signiert und veröffentlicht die Sperrliste.

Der Switch und der AP entscheiden nichts. Sie reichen weiter. Bis sich das Gerät authentifiziert hat, lässt der Port EAP durch und sonst nichts. Der Authenticator packt dieses EAP in RADIUS, schickt es an den Server und handelt nach der Antwort. Ein Access-Accept öffnet den Port. Trägt das Accept Tunnel-Attribute, kommt der Port oder der WLAN-Client in das VLAN, das sie nennen. RFC 3580 legt fest, welche drei Attribute das sind, entliehen aus den Tunnel-Attributen von RADIUS23:

AttributWertBedeutung
Tunnel-TypeVLAN (13)Dieser Tunnel ist ein VLAN
Tunnel-Medium-TypeIEEE-802 (6)In einem 802-Netz
Tunnel-Private-Group-Id"10"Die VLAN-ID, als Zeichenkette gesendet

Die Marke des Switches zählt also weniger, als man denkt. Die Entscheidung liegt auf einer Kiste, die du betreibst. Jeder Hersteller, der dynamische VLANs kann, liest dieselben drei Attribute. Der RFC sagt klar, dass die VLAN-ID „encoded as a string“ ist3, deshalb steht sie in Anführungszeichen.

Warum Zertifikate, und warum nur TLS 1.3

Es gibt mehrere EAP-Methoden, und die meisten, die im Einsatz sind, sind ein Passwort, in TLS verpackt. Für einen Menschen funktioniert das. Für eine Kamera ist es nutzlos, sie hat niemanden, der etwas eintippt, und ein Passwort, das sich tausend Kameras teilen, ist wieder das WLAN-Passwort.

MethodeWas das Gerät beweistWenn das Gerät dem falschen Server vertrautEin Gerät aussperren
EAP-TLSSein eigenes Zertifikat und sein privater SchlüsselDer Server erfährt das Zertifikat, das ohnehin öffentlich istDas Zertifikat widerrufen
PEAP mit MS-CHAPv2Ein Benutzername und ein PasswortDer Server fängt einen Handshake ab, der sich bis zum Passwort zurückknacken lässtDas Passwort ändern
EAP-TTLS mit PAPEin Benutzername und ein PasswortDem Server wird das Passwort selbst geschicktDas Passwort ändern

Die mittlere Spalte ist der Grund für EAP-TLS. MS-CHAPv2 ist seit über einem Jahrzehnt gebrochen. Microsofts eigener Sicherheitshinweis sagt, „an attacker who obtained the MS-CHAP v2 authentication traffic could then use the exploit code to decrypt a user’s credentials“4. Es in PEAP zu verpacken schützt es nur, wenn jedes Gerät prüft, mit wem es redet, und wie der Abschnitt über die Geräte zeigt, tun das viele nicht. Bei EAP-TLS gibt es im Austausch kein Geheimnis zu stehlen. Der private Schlüssel verlässt das Gerät nie5. Nichts zu knacken.

TLS 1.2 schickt das Zertifikat durch die Luft

EAP-TLS über TLS 1.2 hat einen Fehler, den man leicht übersieht. Das Gerät schickt sein Zertifikat, bevor die Verschlüsselung beginnt. Im Klartext. Jeder, der das WLAN mithört, kann es lesen, und da dieser Beitrag die Rolle des Geräts ins Zertifikat schreibt, kann er die auch lesen.

Hier gemessen, indem jede EAP-Nachricht, die das Gerät geschickt hat, nach dem Namen des Geräts und seiner Rolle durchsucht wurde:

LaufTLSGerätename im KlartextRolle OU=staff im Klartext
Identität laptop11.2JaJa
Identität laptop11.3Ja, in der IdentitätNein
Identität anonymous1.3NeinNein

TLS 1.3 behebt das. RFC 9190 sagt, dass mit TLS 1.3 „the use of privacy is mandatory and does not cause any additional round trips“6. Die äußere Identität geht weiter im Klartext, also sollte auch sie das Gerät nicht nennen. Der RFC empfiehlt eine anonyme Identität6. Hier braucht nichts eine echte, weil FreeRADIUS aus dem Zertifikat liest, wer das Gerät ist. Mit anonymous außen ist die letzte Zeile das, was ein Lauscher bekommt: nix.

Dieser Aufbau läuft also nur mit TLS 1.3. FreeRADIUS ist an beiden Enden darauf festgelegt. Ein Gerät, das es nicht spricht, kommt nicht hinein. Das hat einen Preis, und der wird im Abschnitt über die Geräte gezählt statt versteckt. Die eigene Konfiguration von FreeRADIUS sagt dir immer noch, bei 1.2 zu deckeln, in Großbuchstaben, weil „most EAP supplicants WILL NOT DO TLS 1.3 PROPERLY“7. Ehre, wem Ehre gebührt: diese Konfiguration ist die ehrlichste Dokumentation in diesem ganzen Beitrag, und fast jede Falle weiter unten steht irgendwo darin, wenn du die Kommentare liest. In einem Netz, in dem du die Geräte aussuchst, ist das ein Grund, bessere Geräte auszusuchen.

Die Rolle ins Zertifikat schreiben

Das VLAN hängt davon ab, was das Gerät ist, und das Zertifikat ist der Ort dafür. step-ca kann es mit einer Vorlage hineinschreiben, demselben Mechanismus, den der Beitrag über Client-Zertifikate für den CRL-Verteilungspunkt benutzt. Der Start der CA selbst steht in step-ca im Container, und hier geht es von dort aus weiter, mit einer CA namens Home Network CA.

Jedes Gerätezertifikat bekommt seine Rolle in die Organisationseinheit (OU) seines Subjects. Die Vorlage nimmt die Rolle aus der Anfrage und verweigert alles, was nicht auf der Liste steht8:

{{- $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"]
}

Gib ihr einen eigenen Provisioner, damit Geräte nie vom Allzweck-Provisioner admin kommen, mit einem Jahr als längstem Zertifikat, das er signiert:

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

--x509-template kopiert die Vorlage in ca.json, statt auf die Datei zu zeigen, also ändert eine spätere Bearbeitung von device.tpl nichts, bis du wieder step ca provisioner update network --x509-template templates/device.tpl ausführst. Dann stell jedem Gerät sein Zertifikat mit seiner Rolle aus:

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

Eine fehlende oder unbekannte Rolle bekommt nichts:

$ 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 heißt unsicher, weil es das ist, was der Anfragende schickt. Hier ist das in Ordnung. Der Provisioner network hat ein Passwort, nur du hast es, und wer damit signieren kann, kann ohnehin jedes beliebige Zertifikat erstellen, also bringt ihm eine selbst eingetippte Rolle nichts zusätzlich.

Der RADIUS-Server braucht auch ein Zertifikat, damit Geräte prüfen können, mit wem sie reden. Es kommt wie jedes andere Serverzertifikat vom Provisioner admin und lebt die voreingestellten 24 Stunden, erneuert von step ca renew --daemon, so wie es der step-ca-Beitrag für jeden Dienst macht, der kein ACME kann. Geräte prüfen den Namen und die CA, nicht die Seriennummer. Eine tägliche Erneuerung ändert für sie nichts.

ZertifikatProvisionerLaufzeitErweiterte SchlüsselverwendungTrägt
radius.home.exampleadmin24 Stunden, täglich erneuertServer und ClientDen Namen, den Geräte prüfen
laptop1network1 JahrNur ClientOU=staff
cam1network1 JahrNur ClientOU=iot
tv1network1 JahrNur ClientOU=media
switch1, ap1nas1 JahrNur ClientOU=nas, für RadSec; siehe unten

FreeRADIUS: das VLAN entscheiden

FreeRADIUS 3.2.10, im eigenen Container-Image des Projekts. Drei Stücke Konfiguration erledigen die Arbeit: das EAP-Modul, eine Richtlinie, die das VLAN wählt, und eine Liste von Ausnahmen.

Das EAP-Modul

Nur EAP-TLS, nur TLS 1.3, und die CRL bei jeder Anmeldung geprüft:

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
	}
}
DateiEnthält
radius.crtDas Serverzertifikat, dann die Zwischen-CA von step-ca, so wie step ca certificate sie schreibt
radius.keySeinen privaten Schlüssel; wie alles hier für den Benutzer freerad lesbar, sonst startet FreeRADIUS nicht
ca/Den Root, die Zwischen-CA und die CRL der Zwischen-CA, gehasht mit openssl rehash ca/

Die mittlere Zeile hat hier den ersten Start verhindert, allerdings nicht wegen des Schlüssels: Permission denied beim Root-Zertifikat, weil jede Datei aus der CA nur für ihren Besitzer lesbar herausgekommen war. FreeRADIUS läuft als freerad, also müssen alle drei für ihn lesbar sein.

check_crl = yes prüft das Zertifikat des Geräts gegen die CRL der Zwischen-CA. Die Zwischen-CA selbst prüft es nicht, also braucht FreeRADIUS, anders als nginx im Beitrag über Client-Zertifikate, keine Root-CRL. check_all_crl schaltet das ein, wenn du es willst7. ca_path_reload_interval ist wichtiger, als es aussieht, und hat weiter unten einen eigenen Abschnitt.

Die Richtlinie

Jedes Zertifikat, das diese CA je ausgestellt hat, würde die TLS-Prüfung bestehen. Die Webserver, die ihres über ACME bekommen haben, eingeschlossen. Also wirft die Richtlinie alles hinaus, was nicht für diese Aufgabe gemacht wurde: keine Client-Auth-Verwendung, oder keine Rolle, und es wird abgewiesen.

Wie FreeRADIUS das VLAN wähltTLS-1.3-Handshake, CRLClient-Auth-Verwendung?Auf der Geräteliste?Rolle im Zertifikat?Keine Rolle: abweisenAbweisenListe gewinnt: cam1, VLAN 99staff, 10iot, 20media, 30janeinkeineneinja
Zuerst wird die Liste pro Gerät gefragt, und sie gewinnt, wenn sie eine Antwort hat. Sonst wählt die Rolle im Zertifikat das VLAN. Ein Zertifikat ohne beides wird abgewiesen, und das hält die Webserver-Zertifikate der CA aus dem Netz.
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
	}
}

Das kommt nach policy.d/vlan, und eine Zeile, vlan_by_certificate, kommt an den Anfang des Abschnitts post-auth in sites-available/default. In Post-Auth stehen die Details des Zertifikats zur Verfügung, sobald der TLS-Handshake fertig ist9.

Die Ausnahmeliste

Die Rolle entscheidet über die meisten Geräte. Nicht über alle. Die Liste ist für die wenigen, bei denen sie es nicht soll: eine Kamera, die sich seltsam benimmt und in Quarantäne gehört, bevor du herausfindest, warum, oder ein Gerät, das für eine Woche verliehen ist. Es ist ein passwd-Modul, das eine Datei mit zwei Spalten liest, mit dem Common Name des Zertifikats als Schlüssel:

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

Das = vor dem Attribut legt es in die Antwort statt in die Prüfeinträge10. Ein Gerät umzusetzen ist eine Zeile in einer Textdatei, ohne ein Zertifikat neu auszustellen und ohne etwas am Gerät anzufassen.

VLANRolleWas dort lebt
10staffLaptops und Telefone, denen du vertraust
20iotKameras, Sensoren, alles, was nach Hause telefoniert
30mediaFernseher und Streaming-Geräte
99Nur ListeQuarantäne: höchstens Internet, nichts Lokales

Beweisen, bevor du einen Switch anfasst

eapol_test kommt mit wpa_supplicant und spielt Gerät und Switch zugleich, direkt im Gespräch mit dem RADIUS-Server. Benutz es zuerst. Wenn später etwas nicht funktioniert, weißt du, welche Hälfte du dir ansehen musst.

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>

Die Antworten, aus dem eigenen Debug-Log von FreeRADIUS:

(8) Sent Access-Accept
(8)   Tunnel-Private-Group-Id = "10"
(8)   Tunnel-Type = VLAN
(8)   Tunnel-Medium-Type = IEEE-802
GerätRolle im ZertifikatAuf der ListeAntwortVLAN
laptop1staffNeinAccept10
cam1iot99Accept99
tv1mediaNeinAccept30
laptop2staff, widerrufenNeinReject, certificate revokedKeins
radius.home.exampleKeineNeinReject, keine RolleKeins
laptop1 mit ausgeschaltetem TLS 1.3staffNeinReject, protocol versionKeins

Die letzte Zeile solltest du kennen, bevor du live gehst. wpa_supplicant 2.11 wird immer noch mit für EAP ausgeschaltetem TLS 1.3 ausgeliefert, beschrieben als „experimental - disabled by default“11. Lass die phase1-Zeile weg, und ein völlig einwandfreier Laptop wird abgewiesen.

Ein Switch-Port, simuliert

eapol_test lässt den Switch aus. Um die Hälfte des Switches zu sehen, hat hostapd einen Treiber für Kabel, der eine Linux-Schnittstelle in einen 802.1X-Port verwandelt. Ein virtuelles Kabel verbindet ihn mit einem wpa_supplicant, der den Laptop spielt, und er zeigt auf dasselbe 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>

Die Sicht des Ports auf einen guten Laptop und einen widerrufenen:

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

Schalt seine Behandlung dynamischer VLANs ein, und er meldet das VLAN, das er bekommen hat, vlan=10 für den Laptop und vlan=99 für die Kamera in Quarantäne, und sagt dann, er could not add dynamic VLAN interface. Das ist ein Linux-Port, der ehrlich ist: er kann auf- und zumachen, aber sich nicht selbst in ein VLAN verschieben. Ein Managed Switch macht diesen Teil in Hardware, und das ist die Hälfte, die die Herstellereinstellungen weiter unten abdecken.

Widerruf: die CRL, die FreeRADIUS bei einem HUP ignoriert

Ein verlorener Laptop ist der Fall, für den das alles existiert. Widerruf ihn an der CA. Der Provisioner admin signiert ein Widerrufstoken für die Seriennummer, und das funktioniert ohne den Schlüssel des Laptops, denn den Laptop wirst du nicht haben:

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 baut seine CRL sofort neu, wenn generateOnRevoke an ist, eingerichtet so, wie es der Beitrag über Client-Zertifikate zeigt. Kopier sie in das Verzeichnis ca/ von FreeRADIUS, und der Laptop ist draußen. Nur ist er es nicht. Er ist geradewegs wieder hereinspaziert.

Was getan wurde, nachdem die neue CRL auf der Platte lagWiderrufener Laptop
NichtsKommt weiter hinein
kill -HUPKommt weiter hinein. Das Log sagt HUP - No files changed. Ignoring
Über ca_path_reload_interval hinaus gewartetAbgewiesen, certificate revoked
FreeRADIUS neu gestartetAbgewiesen, certificate revoked

Ein HUP lädt Konfigurationsdateien neu, und eine CRL in ca_path gehört nicht dazu, also prüft FreeRADIUS seine Liste von Konfigurationsdateien, findet keine geändert und macht mit der alten CRL im Speicher weiter. Was sie aufnimmt, ist ca_path_reload_interval. In der Standardkonfiguration ist das aus. Setz es unter 300 Sekunden, und FreeRADIUS übergeht dich beim Start:

ca_path_reload_interval is set too low, reset it to 300

Also 300. Ein Widerruf greift binnen fünf Minuten, nachdem die neue CRL angekommen ist. Hol sie per Timer, so wie der Beitrag über Client-Zertifikate es schon für nginx macht, nur in dieses Verzeichnis. Starte FreeRADIUS neu, wenn fünf Minuten für das Gerät, das du gerade verloren hast, zu lang sind.

Die CRL kann auch alle auf einmal aussperren. Also in beide Richtungen. Beides wurde mit guten Zertifikaten getestet:

Zustand der CRLWas FreeRADIUS sagtWer hineinkommt
AktuellNichtsJedes gute Gerät
Fehlt in ca/error 3 : unable to get certificate CRLNiemand
Über ihren Next-Update-Zeitpunkt hinauserror 12 : CRL has expiredNiemand

Die CRL von step-ca gilt standardmäßig 24 Stunden. Bleibt der Timer stehen, der sie holt, scheitert am nächsten Tag jedes Gerät im Netz. allow_expired_crl gibt es, um das abzufedern7. Benutz es nicht. Ein Netz, das alle hereinlässt, weil eine Liste veraltet ist, hat aufgehört, irgendetwas zu prüfen, und daher ist die Lösung, den Timer zu überwachen, nicht die Prüfung abzuschalten.

RADIUS ist immer noch MD5 und ein gemeinsames Geheimnis

Alles bisher schützt das Gespräch zwischen dem Gerät und FreeRADIUS. EAP-TLS läuft durchgehend, und der Switch oder AP in der Mitte kann es nicht lesen. Das RADIUS-Paket, das es trägt, ist eine andere Sache.

Klassisches RADIUS ist UDP mit einem gemeinsamen Geheimnis, und das Geheimnis wird über MD5 benutzt, was deutlich weniger ausmachen würde, wenn das Paket nichts Wertvolles trüge, aber das Access-Accept für einen WLAN-Client trägt die Schlüssel zum Verkehr dieses Clients. MS-MPPE-Recv-Key und MS-MPPE-Send-Key sind der Hauptschlüssel, mit dem der AP den Verkehr dieses Clients verschlüsselt, und RFC 2548 schützt sie mit b(1) = MD5(S + R + A) und einem XOR, wobei S das gemeinsame Geheimnis ist12. Darin steckt keine Forward Secrecy. Zeichne heute den RADIUS-Verkehr auf, hol dir nächstes Jahr das Geheimnis aus einem Konfigurations-Backup oder der laufenden Konfiguration eines Switches, und jeder aufgezeichnete Schlüssel lässt sich entschlüsseln, samt dem WLAN-Verkehr, den er geschützt hat.

Die Frage ist also nicht, ob das Geheimnis lang genug ist. Sie ist, warum die Schlüssel zum Funknetz überhaupt unter MD5 über die Leitung gehen. Das müssen sie nicht.

BedrohungEinfaches RADIUS über UDPMit verlangtem Message-AuthenticatorRadSec, TLS 1.3, Zertifikate auf beiden Seiten
Gefälschtes Access-Accept (BlastRADIUS)Möglich bei Anfragen ohne EAP, MAB eingeschlossenGestopptGestoppt
WLAN-Schlüssel aus einer Aufzeichnung gelesenJeder, der je das Geheimnis erfährtDasselbeBraucht die TLS-Sitzungsschlüssel, die nie gespeichert werden
Eine Kiste im Management-Netz gibt sich als Switch ausBraucht nur das GeheimnisDasselbeBraucht ein Zertifikat mit der Rolle nas, von einer bekannten Adresse
Wer sieht, welches Gerät sich angemeldet hatJeder auf dem WegDasselbeNiemand

BlastRADIUS, und die Zeile, die es stoppt

BlastRADIUS, CVE-2024-3596, lässt einen Angreifer auf dem Weg zwischen Switch und Server ein Reject in ein Accept verwandeln, indem er das MD5 angreift, das die Antwort signiert13. Die Lösung ist das Attribut Message-Authenticator an jeder Anfrage und jeder Antwort13. RFC 3579 hat es schon immer für alles verlangt, was EAP trägt14, also war EAP-TLS nie die weiche Stelle. MAB war es, weil eine Prüfung der MAC-Adresse kein EAP trägt und viele Switches sie ohne geschickt haben.

FreeRADIUS 3.2.10 verlangt es ab Werk, mit require_message_authenticator = yes in radiusd.conf. Hier getestet mit einer von Hand gebauten MAB-Anfrage:

AnfrageAntwort
Kein Message-AuthenticatorKeine Antwort. Geloggt als Packet does not contain required Message-Authenticator attribute
Mit Message-AuthenticatorEine Antwort, Access-Reject für eine unbekannte MAC

Lass diese Einstellung, wie sie ist. Die Konfiguration warnt, dass sie und limit_proxy_state beide auf no zu setzen „will allow MITM attackers to create fake Access-Accept packets to the NAS“15.

Das schließt die Fälschung aus. Für die Schlüssel tut es nichts.

RadSec: RADIUS in TLS 1.3

Der höchste verfügbare Standard ist RadSec, RADIUS über TLS mit einem Zertifikat an jedem Ende16. Der Switch oder AP beweist mit einem Zertifikat, wer er ist, FreeRADIUS beweist mit seinem eigenen, wer es ist, und das ganze RADIUS-Gespräch, Schlüssel eingeschlossen, steckt in TLS 1.3 mit Forward Secrecy. Die IETF bringt es auf den Standards Track; der Entwurf, der den experimentellen RFC von 2012 ersetzen würde, „Has enough positions to pass“17.

Die Switches und APs bekommen ihre Zertifikate von step-ca wie alles andere, von einem dritten Provisioner, dessen Vorlage OU=nas stempelt und sonst nichts, also kann ein Gerätezertifikat nicht benutzt werden, um sich als Switch auszugeben, und ein Switch-Zertifikat, das keine Rolle trägt, die die VLAN-Richtlinie kennt, kann auch nicht benutzt werden, um ins Netz zu kommen.

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/'

Das Geheimnis ist wirklich das Wort radsec. Ehrlich. Über TLS bewirkt es nichts, und FreeRADIUS’ eigenes Beispiel sagt „TLS clients MUST have the shared secret set to ‚radsec‘“18. Zwei Dinge, die naheliegend aussehen, funktionieren nicht, beide auf die harte Tour gefunden. check_cert_cn = %{client:shortname} wird beim Handshake zu nichts expandiert, also wird jeder Switch mit Certificate CN (switch1) does not match specified value () abgewiesen; daher das verify-Skript. Und der Listener startet nicht unter freeradius -X, was Threads abschaltet: Threading must be enabled for TLS sockets to function properly. Debugge es stattdessen mit freeradius -fxx -l stdout.

Jede Strecke zu FreeRADIUS schützenFreeRADIUSPort 2083nur OU=nasOmada, hostapdRadSec eingebautRadSec, TLS 1.3Netgearnur UDPradsecproxyselbes Mgmt-VLANUDP+ Msg-AuthRadSecstep-caalle ZertifikateCRL über HTTPS, Root festgenagelt
Wo jede Strecke geschützt ist. Geräte, die RadSec sprechen, gehen über TLS 1.3 direkt zu FreeRADIUS. Geräte, die nur UDP sprechen, reden mit radsecproxy im selben Management-VLAN, und nur diese kurze Strecke ist einfaches RADIUS. Die CRL kommt von step-ca über HTTPS mit festgenageltem Root.

Der simulierte Switch-Port von vorhin, umgebaut auf RadSec, gegen dieselben Tests:

Switch zeigt vorVonErgebnis
switch1, OU=nasSeiner eingetragenen AdresseTLS 1.3 steht; laptop1 in VLAN 10 zugelassen, widerrufener laptop2 abgewiesen
Das Zertifikat von laptop1Der Adresse des SwitchesIm Handshake abgewiesen; das verify-Skript liefert 1
switch1, OU=nasEiner nicht eingetragenen AdresseIgnoring request … from unknown client

Welche Geräte es können

AuthenticatorRadSecAnmerkungen
hostapd 2.11Ja, wenn damit gebautCONFIG_RADIUS_TLS=y ist in seiner defconfig auskommentiert19. Das Paket von Alpine lässt es aus und lehnt die Konfiguration ab: unsupported RADIUS type 'TLS'. Damit gebaut, funktionierte es auf Anhieb
TP-Link Omada APs und SwitchesAb Controller 6.1, mit aktueller 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-ProxyNeinDer eigene Proxy des Controllers „does not support the RadSec feature“21
Netgear M4250 und M4350Nicht dokumentiertDas CLI-Handbuch erwähnt RadSec nicht und deckelt das gemeinsame Geheimnis: „The secret must be an alphanumeric value not exceeding 16 characters“22
Netgear-APs WAX und WBENicht dokumentiertDie Handbücher beschreiben den RADIUS-Port als UDP und das voreingestellte Geheimnis als sharedsecret23

Darf ich fragen, warum ein Switch, der 2026 als voll gemanagt verkauft wird, nicht über TLS mit seinem RADIUS-Server reden kann? Nicht, wer das entschieden hat. Was im Prozess es zugelassen hat, dass er so ausgeliefert wird, wenn TP-Links Controller, hostapd und ein kostenloser Proxy es alle können. Sechzehn zufällige alphanumerische Zeichen sind etwa 95 Bit, und das ist nicht das, was geknackt wird. Der Punkt ist, dass es der einzige angebotene Schutz ist: keine TLS-Option, ein MD5-Verfahren von 1999, das die WLAN-Schlüssel bewacht, und ein AP, der mit dem Geheimnis auf dem Wort sharedsecret ausgeliefert wird. Bei Netgear-Geräten ist radsecproxy nicht optional.

radsecproxy, für Geräte, die nur UDP sprechen

Wo der Switch oder AP kein RadSec kann, stell radsecproxy daneben. Er nimmt UDP-RADIUS an und schickt RadSec hinaus24, also ist die ungeschützte Strecke die paar Meter Management-VLAN zwischen Switch und Proxy, nicht der ganze Weg zum 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
}

Benutz den Namen. Nicht die Adresse. Mit host 10.89.0.100 hat der Proxy den Server abgelehnt, zu Recht: No subjectaltname matching address 10.89.0.100. Mit dem Namen kam er als TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 hoch, und dieselben Geräte bekamen durch ihn dieselben Antworten: laptop1 VLAN 10, cam1 99, tv1 30, laptop2 abgewiesen. Eine Anfrage ohne Message-Authenticator blieb am Proxy hängen: ignoring request from client switches … missing required message-authenticator.

Halt diese UDP-Strecke sauber. Kurz und eingezäunt. Die Management-Adresse des Switches und der Proxy sitzen in einem Management-VLAN, in das kein Client-VLAN routen kann, und die Firewall sagt das auch.

Auch der Abruf der CRL

Die CRL ist signiert, also kann niemand eine fälschen. Man kann aber eine ältere, noch gültige wieder einspielen, und die verbirgt einen seitdem erfolgten Widerruf. Hol sie von step-ca über HTTPS mit festgenageltem eigenem Root, nicht über einfaches 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

Gegen jeden anderen Root scheitert derselbe Befehl mit curls Exit 60, und FreeRADIUS behält die Liste, die es hat.

Wohin es geht

RFC 9765, RADIUS/1.1, entfernt MD5 ganz aus RADIUS, wenn es über TLS läuft: „the RADIUS shared secret is no longer used, and all MD5-based packet authentication and attribute obfuscation methods are removed“25. Er ist Experimental. FreeRADIUS 3.2 hat es hinter einem Build-Flag, --with-radiusv11, „for testing interoperability“26, und das offizielle Image 3.2.10 ist nicht damit gebaut; die Option ist nicht im Binary. RadSec mit TLS 1.3 ist der höchste Standard, den du heute einsetzen kannst, und RADIUS/1.1 ist der nächste Schritt.

Jeder Managed Switch, der etwas taugt, kann 802.1X mit dynamischen VLANs, und die Einstellungen laufen auf dieselben fünf Dinge hinaus, egal was auf dem Gehäuse steht. Der Unterschied zwischen diesen beiden liegt im Detail, und ein Detail auf der TP-Link-Seite ändert, wie du einen Schreibtisch verkabelst.

EinstellungNetgear M4250 und M4350TP-Link-Omada-Switches
RADIUS-ServerSecurity > Management Security > RADIUS > Server Configuration27Ein RADIUS Profile im Omada-Controller28
802.1X anSecurity > Port Authentication > Basic > 802.1X Configuration: Dot1x Admin Mode, Authentication Admin Mode27Authentication > 802.1X, Authentication Type „Port Based“28
VLAN von RADIUSVLAN Assignment Mode, „The default is Disable“27„enable ‘VLAN Assignment’“28
Fehlendes VLANWird nicht angelegt, außer Dynamic VLAN Creation Mode ist an, ebenfalls standardmäßig aus27„the switch will create the related VLAN automatically“29
Access-PortsControl Mode Auto802.1X Control mode Auto28
Uplinks, der Port des RADIUS-Servers, AP-PortsForce Authorized802.1X aus gelassen: „Keep 802.1X authentication disabled on ports connected to the authentication server, the internet and the management computer“29

Leg die VLANs so oder so zuerst auf dem Switch an. Ein VLAN, das existiert, weil eine Anmeldung es herbeigezaubert hat, ist ein VLAN, das niemand konfiguriert und für das niemand eine Firewall-Regel geschrieben hat.

TP-Links eigene Anleitung sagt „VLAN Assignment takes effect only when the control type is Port Based“29. Port Based heißt, das erste Gerät, das sich authentifiziert, legt das VLAN für den ganzen Port fest. Häng einen Tisch-Switch oder ein Telefon mit einem durchgeschleiften PC an diesen Port, und das zweite Gerät fährt im VLAN des ersten mit, ohne je nach einem Zertifikat gefragt zu werden, was an einem Port der Belegschaft heißt, dass alles, was hinten in diesem Telefon steckt, im Netz der Belegschaft ist.

Also bei TP-Link: ein Gerät pro Access-Port. Nichts zwischen der Wanddose und dem Gerät. Der MAC-basierte Modus von Netgear authentifiziert jedes Gerät an einem Port einzeln; das Handbuch des GS710TUP beschreibt ihn so, dass er „multiple supplicants connected to the same port“ erlaubt30.

MAB, für den Drucker, der nichts davon kann

Manche Geräte haben überhaupt keinen 802.1X-Client. MAC Authentication Bypass lässt den Switch die MAC-Adresse des Geräts als Benutzername und Passwort an RADIUS schicken, und beide Hersteller unterstützen es zusammen mit 802.1X am selben Port. Netgear versucht zuerst 802.1X und fällt dann zurück: „if a client tries 802.1X authentication… and authentication fails, the interface proceeds with MAB authentication“27. TP-Link schickt die MAC als „12 hexadecimal digits, all lowercase and no punctuation“29.

Eine MAC-Adresse ist keine Identität. Jeder kann seine Netzwerkkarte mit einem Befehl auf die MAC des Druckers setzen, von einem mitgebrachten Laptop aus, während er neben dem Drucker steht, dessen Kabel er sich gerade geborgt hat. Also bekommt MAB eine eigene Liste, und die Richtlinie gibt ihm immer nur das IoT-VLAN oder die Quarantäne:

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
		}
	}
}

Das kommt als Erstes in authorize, mit einem passwd-Modul namens mab_vlan, das Zeilen mac,vlan genau wie die Geräteliste liest. Die Zertifikatsrichtlinie ist in if (&EAP-Message) eingepackt, damit sie MAB in Ruhe lässt. Getestet:

MAB-AnfrageListeneintragAntwort
aabbccddeeff20Accept, VLAN 20
112233445566KeinerReject
ccddeeff001110, das VLAN der BelegschaftReject. Die Richtlinie setzt keine MAC ins Netz der Belegschaft, egal was die Liste sagt

Wer den Drucker fälscht, bekommt das VLAN des Druckers und nichts weiter. Mehr sollte eine MAC-Adresse nie einbringen.

Die Omada-Switch-Einstellungen oben wurden außerdem gegen einen simulierten Omada Software Controller geprüft, 6.2.14.11, in einem Container ohne Hardware dahinter. Seine englischen Oberflächentexte nennen jede hier benutzte Einstellung: die Option RadSec, bei der „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“ und den Import von Client-Zertifikat und Schlüssel für RadSec. Ein Controller konfiguriert; er leitet keinen Verkehr, also beweist das, dass es die Einstellungen in dieser Version gibt, und nichts weiter. Für Netgear gibt es nichts Vergleichbares zum Ausführen, also stammt seine Spalte aus Netgears eigenen Handbüchern und wurde hier nicht getestet.

Der Access Point: eine SSID, jedes VLAN

Am Access Point zahlt sich die einzelne SSID aus. Ein WPA-Enterprise-Netz, und jeder Client kommt in das VLAN, das RADIUS für ihn nennt. Der AP taggt den Verkehr dieses Clients mit seinem VLAN und schickt ihn das Kabel hinauf, also muss der Switch-Port, der den AP speist, jedes VLAN tragen, das RADIUS vergeben könnte, getaggt. Lass eins weg, und das Gerät authentifiziert sich einwandfrei und bekommt nie eine Adresse.

Eine SSID, simuliert

Bevor ich dem Wort eines der beiden Hersteller vertraut habe, lief das Ganze auf simulierten Funkkarten. Linux’ mac80211_hwsim erzeugt virtuelle WLAN-Karten, die echtes WPA-Enterprise machen. Der Kernel dieser Maschine liefert das Modul nicht mit, also hat der Test denselben Kernel in einer kleinen virtuellen KVM-Maschine gebootet und es dort geladen, aus Fedoras eigenem signiertem Paket, mit hostapd 2.11 als Access Point und drei wpa_supplicant-Stationen in eigenen Network Namespaces31:

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 heißt „erforderlich“: eine Anmeldung ohne VLAN in der Antwort wird abgewiesen, statt in einem Standard-VLAN abgeladen zu werden. Jede Station hat sich als anonymous über TLS 1.3 angemeldet, und das eigene Log des AP zeigt, was RADIUS ihm gesagt hat:

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)
Eine SSID, drei VLANslaptop1OU=staffcam1auf der Listetv1OU=mediaAccess PointSSID "Home"VLAN 10, Schlüssel AVLAN 99, Schlüssel BVLAN 30, Schlüssel CSwitchTrunk-Port10, 30, 99 getaggtein Kabel
Eine SSID, drei Geräte, drei VLANs. Der AP setzt jeden Client auf seine eigene VLAN-Schnittstelle mit eigenem Gruppenschlüssel und schickt alle drei über einen getaggten Trunk hinauf. Der Switch-Port, der den AP speist, muss jedes VLAN tragen, das RADIUS nennen kann.
StationZertifikatVLANBridgeEigenes GatewayVLAN des Fernsehers
laptop1OU=staff10brvlan10ErreichbarKeine Antwort
cam1OU=iot, auf der Liste99brvlan99Erreichbar
tv1OU=media30brvlan30Erreichbar

Die Zeilen mit dem Gruppenschlüssel sind der Teil, den die Leute übersehen. Broadcast- und Multicast-Frames im WLAN werden mit einem Gruppenschlüssel verschlüsselt, den sich alle im Netz teilen, und würden sich alle drei VLANs einen teilen, könnte die Kamera die Broadcasts des Laptops aus der Luft lesen, egal was der Switch dachte. hostapd führt einen eigenen Gruppenschlüssel pro VLAN, und das Log zeigt, wie es für jedes einen startet. Derselbe Lauf in WPA3-Enterprise, WPA-EAP-SHA256 mit verlangtem Schutz der Management-Frames, ergab dieselben drei VLANs und dieselben drei Gruppenschlüssel.

EinstellungNetgear WAX und WBETP-Link Omada EAP
SicherheitWPA2 Enterprise oder WPA3 EnterpriseWPA2-Enterprise oder WPA3-Enterprise, einschließlich eines Only-Modus mit CCMP-128
VLAN von RADIUSDynamic VLAN, Enable23„Enable VLAN Assignment for Wireless Network“28
Kein VLAN in der AntwortClient kommt in das eigene VLAN der SSID23Nicht dokumentiert; FreeRADIUS schickt hier nie ein Accept ohne
RADIUS-TransportNur UDP, also radsecproxyRadSec ab Controller 6.1 mit aktueller Firmware20

Zwei Netgear-Details solltest du kennen, bevor sie zubeißen. Ein Client, dem der RADIUS-Server kein VLAN gibt, wird „automatically assigned the VLAN ID that you configured for the SSID“23, also setz das eigene VLAN der SSID auf Quarantäne, nicht auf die Belegschaft; FreeRADIUS schickt hier immer eins, aber der Rückfall sollte trotzdem geschlossen scheitern. Und dynamisches VLAN „is mutually exclusive with a captive portal, NAT mode, wireless client isolation, and a multicast DNS (mDNS) gateway“23, also fällt ein Gastportal in derselben SSID weg.

WPA3-Enterprise auf dem WAX620 ist der 192-Bit-Modus: „the encryption is automatically set to GCMP256“32, mit verlangtem Schutz der Management-Frames. Clients, die kein GCMP-256 können, treten nicht bei. Teste dein ältestes Gerät, bevor du umschaltest, genau wie bei TLS 1.3.

Die Geräte, und die Prüfung, die alle auslassen

Jedes Gerät braucht drei Dinge: sein eigenes Zertifikat und seinen Schlüssel, das Root-Zertifikat und den Namen des RADIUS-Servers, den es erwarten soll. Die meisten Anleitungen behandeln die ersten beiden. Lass beim dritten was weg, und es funktioniert trotzdem, und genau das ist das Problem.

Das dritte ist das, was ein Gerät davon abhält, dem falschen Server zu vertrauen. Hier wurde zweimal ein falscher RADIUS-Server aufgestellt: einmal mit einem völlig gültigen Zertifikat derselben CA, ausgestellt auf ha.home.example, und einmal mit einem selbstsignierten Zertifikat. Ein Laptop wurde mit drei verschiedenen Client-Einstellungen auf jeden davon gerichtet:

Client prüftEchter ServerDieselbe CA, anderer NameSelbstsigniert
Die CA und den ServernamenVerbindet sichAbgewiesen, Domain mismatchAbgewiesen
Nur die CAVerbindet sichVerbindet sichAbgewiesen
NichtsVerbindet sichVerbindet sichVerbindet sich

Schau auf die mittlere Zeile. Deine CA signiert ein Zertifikat für jeden Webserver im Haus. Ein Gerät, das nur die CA prüft, vertraut jedem davon als RADIUS-Server, also kann jeder, der an einen dieser Schlüssel kommt, von einer vergessenen Testkiste oder einem Webserver, den niemand gepatcht hat, einen Access Point mit deiner SSID aufstellen, und deine Geräte treten ihm ohne ein Murren bei. Mit EAP-TLS geben sie kein Passwort heraus, aber sie sind im Netz des Angreifers und vertrauen ihm. TLS 1.3 ändert daran nichts; dieselben drei Zeilen kamen unter 1.3 zurück. Nur die Prüfung des Namens tut es.

Android war zuerst da. Die Option „Do not validate“ wurde „for security reasons“ entfernt33, wobei vermerkt ist, dass „saved enterprise configurations that are set up to turn off server certificate validation aren’t affected“33. Ein altes Telefon kann also noch eine mit sich herumtragen.

Linux (NetworkManager)macOS, iOS, iPadOSAndroid
Zertifikat und Schlüssel802-1x.client-cert, 802-1x.private-keyIdentity Payload in einem KonfigurationsprofilAls WLAN-Zertifikat installiert
Root-Zertifikat802-1x.ca-certCertificate Payload, vertraut über PayloadCertificateAnchorUUID34Feld für das CA-Zertifikat
Servername802-1x.domain-match35TLSTrustedServerNames34Feld Domain; setDomainSuffixMatch in der API36
TLS 1.3802-1x.phase1-auth-flags tls-1-3-enableTLSMinimumVersion und TLSMaximumVersion "1.3"; das Maximum steht standardmäßig auf "1.2"34setMinimumTlsVersion(TLS_V1_3), ab API-Level 34, wo das Gerät es unterstützt36

Unter Linux ist das Ganze ein Befehl. Geprüft wurde er mit nmcli --offline, das die Verbindung ausgibt, die es anlegen würde, ohne das laufende System anzufassen:

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

Für einen Kabelport ist es dasselbe mit type ethernet und ohne SSID. Dass Apples maximale TLS-Version standardmäßig auf 1.2 steht, ist die Falle auf dieser Seite: ein Profil, das sie weglässt, baut einen Mac, den dieses Netz abweist.

Was „nur TLS 1.3“ kostet

Die ehrliche Grenze dieses Aufbaus sind die Geräte, die es nicht können. Eine Kamera oder ein Fernseher mit einem eingebetteten Supplicant von vor Jahren schafft vielleicht EAP-TLS über 1.2 und nie über 1.3, und es gibt keine Einstellung auf deiner Seite, die das behebt. Die TLS-Version wird ausgehandelt, bevor FreeRADIUS das Zertifikat gesehen hat, also kann es keine Ausnahme für die IoT-Rolle machen.

Teste also jedes Gerät, bevor du es kaufst. Nicht danach. Richte es auf ein FreeRADIUS, das nur 1.3 spricht, und lies das Log: recv TLS 1.3 Handshake, ClientHello oder protocol version sagt dir alles, was du wissen musst. Ein Gerät, das scheitert, kommt an einen Kabelport mit MAB oder gar nicht ins Netz. Das ganze Netz zu schwächen, um dem schlechtesten Ding darin entgegenzukommen, ist genau der Weg, auf dem das gemeinsame Passwort angefangen hat.

Die komplette Konfiguration, Datei für Datei

Alles oben ist auf die Zeilen gekürzt, die den jeweiligen Punkt machen. Hier ist alles. Jede Datei lief im Labor, von einem leeren step-ca und einem FreeRADIUS-Baum im Auslieferungszustand bis zu einem Laptop in VLAN 10, und die Kopien unten sind genau diese Dateien, nur mit den Adressen aus der Tabelle statt denen aus dem Labor. Hol sie dir als einen Download, eap-tls-configs.tar.gz, oder Datei für Datei über die Links.

NameAdresseWas es ist
ca.home.exampleNur über den Namenstep-ca, Port 9000
radius.home.example192.0.2.10FreeRADIUS, RadSec auf 2083
radsecproxy192.0.2.11radsecproxy, UDP rein, RadSec raus
switch1, ap1192.0.2.21, 192.0.2.31Geräte, die RadSec sprechen
Geräte nur mit UDP192.0.2.32 bis .63Über radsecproxy; der Linux-AP unten ist ap2 auf .33
DateiKommt nachIhre Aufgabe
step-ca/templates/device.tpl/home/step/templates/ in der CARolle in die OU, nur Client-Authentifizierung
step-ca/templates/nas.tpl/home/step/templates/ in der CAOU=nas, für RadSec
step-ca/setup.shEinmal in der CA ausführenLegt die Provisioner network und nas an
step-ca/issue.shDort ausführen, wo step der CA vertrautJedes Zertifikat, und das Widerrufsrezept
freeradius/install.shAus freeradius/ ausführenBringt den Rest an seinen Platz
freeradius/mods-available/eapRADDB/mods-available/EAP-TLS, TLS 1.3, CRL geprüft
freeradius/mods-available/device_vlanRADDB/mods-available/Liest die Liste pro Gerät
freeradius/mods-available/mab_vlanRADDB/mods-available/Liest die MAB-Liste
freeradius/mods-config/device_vlan/devicesRADDB/mods-config/device_vlan/Zertifikatsname auf VLAN
freeradius/mods-config/device_vlan/mabRADDB/mods-config/device_vlan/MAC-Adresse auf VLAN
freeradius/policy.d/vlanRADDB/policy.d/Wählt das VLAN für ein Zertifikat
freeradius/policy.d/mabRADDB/policy.d/Wählt das VLAN für eine MAC, nur 20 oder 99
freeradius/sites-available/default.patchAuf RADDB angewendetRuft die beiden Richtlinien auf
freeradius/sites-available/tlsRADDB/sites-available/RadSec-Listener und seine Clients
freeradius/nas-only.shRADDB/Lässt nur OU=nas-Zertifikate herein
freeradius/clients.confRADDB/UDP zum Testen auf der Kiste selbst
systemd/radius-crl-refresh/usr/local/sbin/Holt und prüft die CRL
systemd/radius-crl-refresh.service/etc/systemd/system/Führt es aus
systemd/radius-crl-refresh.timer/etc/systemd/system/Jede Stunde
systemd/radius-cert-renew.service/etc/systemd/system/Erneuert das Serverzertifikat
radsecproxy/radsecproxy.conf/etc/radsecproxy.confUDP rein, RadSec raus
hostapd/hostapd-wired.conf/etc/hostapd/Ein 802.1X-Switch-Port unter Linux
hostapd/hostapd-wifi.conf/etc/hostapd/Ein Access Point unter Linux, eine SSID
clients/wpa_supplicant-wired.confDas GerätAnmeldung am Kabel
clients/wpa_supplicant-wifi.confDas GerätAnmeldung im WLAN
clients/nmcli.shDas GerätDasselbe, über NetworkManager

FreeRADIUS liegt auf jeder Distribution woanders, und seinen Dateien ist das egal, weil jeder Pfad darin aus dem Konfigurationsverzeichnis gebaut wird. install.sh nimmt dieses Verzeichnis als RADDB. Es lief auf allen dreien unten, und die eigene Konfigurationsprüfung von FreeRADIUS, -XC, ging auf jedem durch. Nur das offizielle Image hat danach jeden Test in diesem Beitrag durchlaufen; bei den anderen beiden ist diese Prüfung der ganze Beleg, in einem Container, also war SELinux auf einer echten Fedora-Kiste nicht Teil davon.

Offizielles ImageDebian 13Fedora
FreeRADIUS3.2.103.2.73.2.8
RADDB/etc/freeradius/etc/freeradius/3.0/etc/raddb
Läuft alsfreeradfreeradradiusd
DienstDer Containerfreeradiusradiusd
GetestetAlles in diesem Beitraginstall.sh und -XCinstall.sh und -XC

Die beiden systemd-Units nennen /etc/freeradius und den Dienst freeradius. Auf Debian oder Fedora änderst du beides passend zur Tabelle. Sonst bewegt sich nichts.

step-ca

Die Gerätevorlage ist die aus Die Rolle ins Zertifikat schreiben, Zeile für Zeile, mit einem Kommentar obendrauf. Die Vorlage nas ist dieselbe Idee mit fester Rolle:

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 legt beide Provisioner an. Führ es einmal im step-ca-Container aus. Es fragt für jeden nach einem Passwort, und den CRL-Block, den es ausgibt, trägst du von Hand in ca.json ein:

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 stellt jedes Zertifikat in diesem Beitrag aus und behält das Widerrufsrezept aus dem Abschnitt zum Widerruf als Kommentar für den Tag, an dem du es brauchst. Dieses Rezept lief auch gegen diese CA, an einem eigens dafür erstellten laptop2:

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

Führ install.sh aus dem Verzeichnis freeradius des Downloads aus. Es kopiert die Dateien hinein, patcht die Standard-Site und legt die Links an. Mehr tut es nicht:

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"

Der Patch sind zwei Zeilen in der mitgelieferten Standard-Site. Er wurde gegen 3.2.10 erstellt und lässt sich auf 3.2.7 und 3.2.8 mit nichts Schlimmerem als einem Versatz anwenden:

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 ersetzt das mitgelieferte. Komplett:

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 ist die Richtlinie von vorhin, mit der Hülle if (&EAP-Message), die der MAB-Abschnitt erwähnt:

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 ist die Richtlinie aus MAB, für den Drucker, der nichts davon kann, und mods-available/device_vlan und nas-only.sh sind wie weiter oben gezeigt, alle drei mit einem zusätzlichen Kommentar. Das Modul der MAB-Liste und die beiden Listen:

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 ersetzt das mitgelieferte RadSec-Beispiel. Das temporäre Verzeichnis für verify ist ${run_dir}, das schon existiert und dem Benutzer gehört, als der FreeRADIUS läuft, also gibt es nichts anzulegen:

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 ersetzt die mitgelieferte und lässt eapol_test auf der Kiste selbst herein und sonst nichts. Switches und APs kommen über RadSec oder durch den 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
}

Prüf zuerst die Konfiguration. Dann starte es im Vordergrund, mit Threads, damit RadSec funktioniert:

freeradius -XC
freeradius -fxx -l stdout

Aktuell halten

radius-crl-refresh holt die CRL mit fest verankerter Root und weist eine zurück, die nicht von der Zwischen-CA signiert oder schon abgelaufen ist, sodass ein schiefgegangener Abruf die alte Liste an ihrem Platz lässt. Eine gute Liste tauscht es nie gegen Müll:

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

Das Serverzertifikat lebt 24 Stunden, also erneuert es sich selbst. Die Erneuerung hat hier den Schlüssel und die Rechte beider Dateien behalten, und das zählt, weil FreeRADIUS sie als freerad liest. Es startet FreeRADIUS neu, statt ein HUP zu schicken. Derselbe Grund wie bei der 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

Eine Linux-Kiste als Ersatz für einen Switch-Port, die RadSec spricht. Das ist die Datei, mit der der simulierte Port lief:

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

Und für einen Access Point. Dieser geht über UDP durch radsecproxy, so wie es ein Netgear-AP tun würde, und ist die Datei, mit der die simulierten Funkmodule liefen:

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

Die Geräte

nmcli.sh ist der Befehl aus Die Geräte, und die Prüfung, die alle auslassen und sein Zwilling für den Kabelport. Ohne NetworkManager nimmt wpa_supplicant diese hier:

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"
}

Wogegen jede Datei getestet wurde

DateienGetestet gegenErgebnis
step-ca/step-ca 0.30.2, eine neue CAAlle 7 Zertifikate mit ihren Rollen ausgestellt; laptop2 nach dem Rezept ausgestellt und widerrufen
freeradius/3.2.10, installiert mit install.shlaptop1 VLAN 10, cam1 99, tv1 30, ein nas-Zertifikat abgewiesen; MAB aabbccddeeff VLAN 20, eine unbekannte MAC abgewiesen
radius-crl-refreshstep-ca über TLS 1.3CRL installiert und gehasht; der widerrufene Laptop binnen fünf Minuten abgewiesen, ohne Neustart
radius-cert-renew.servicestep ca renew 0.30.2Zertifikat erneuert; Schlüssel und Rechte unberührt
radsecproxy.confradsecproxy 1.11.2EAP-TLS durch ihn hindurch angenommen; MAB mit Message-Authenticator VLAN 20, ohne verworfen
hostapd-wired.confhostapd 2.11, mit RadSec gebautVLANs 10, 99 und 30 über TLS 1.3
hostapd-wifi.conf, wpa_supplicant-wifi.confSimulierte Funkmodule, über radsecproxyDrei Stationen an einer SSID, VLANs 10, 99 und 30; die Client-Datei wie geschrieben und eine reine WPA3-Station sind beide beigetreten
wpa_supplicant-wired.confeapol_test 2.11Dieselben Antworten wie in der Tabelle weiter oben
systemd-Unitssystemd-analyze verifyNur der Hinweis, dass die beiden Programme hier nicht installiert sind

Was alle aussperrt

Jeder dieser Fälle ist beim Aufbau aufgetreten oder wurde getestet, abgesehen vom fehlenden VLAN auf einem Trunk, das einen echten Switch braucht, um sich zu zeigen. Fast alle scheitern geschlossen. Das ist die richtige Art zu scheitern, und trotzdem ein schlechter Morgen.

FehlerWas du siehstLösung
CRL abgelaufen oder fehltJedes Gerät abgewiesen; CRL has expired oder unable to get certificate CRLDie CRL per Timer holen und überwachen
Neue CRL nicht aufgenommenEin widerrufenes Gerät kommt weiter hineinca_path_reload_interval = 300, oder FreeRADIUS neu starten
Client hat TLS 1.3 ausprotocol version, Gerät abgewiesentls_disable_tlsv1_3=0, tls-1-3-enable oder die Schlüssel im Apple-Profil
Zertifikatsdateien für freerad nicht lesbarFreeRADIUS startet nicht, Permission deniedFür freerad lesbar machen, den Schlüssel nur für ihn
Zertifikat ohne RolleVon der Richtlinie abgewiesenÜber den Provisioner network mit --set role= neu ausstellen
VLAN nicht auf dem Switch oder dem Trunk des APGerät authentifiziert sich, bekommt keine AdresseDas VLAN anlegen und auf jedem Trunk taggen, den das Gerät erreichen kann
Falsches gemeinsames RADIUS-GeheimnisKeine Antwort; FreeRADIUS loggt invalid Message-Authenticator! (Shared secret is incorrect.), und der Switch wiederholt, bis er aufgibtDasselbe Geheimnis in clients.conf und auf dem Switch oder AP
Gerät prüft den Servernamen nichtNichts. Es funktioniert, und es würde auch mit einem Betrüger funktionierenDen Servernamen auf jedem Gerät setzen
Anfrage ohne Message-AuthenticatorStillschweigend verworfen; der Switch wiederholt und gibt aufAm Switch einschalten, oder radsecproxy davorsetzen
RadSec-Listener unter freeradius -XThreading must be enabled for TLS socketsfreeradius -fxx -l stdout
hostapd ohne RadSec gebautunsupported RADIUS type 'TLS'Mit CONFIG_RADIUS_TLS=y bauen, oder radsecproxy benutzen
radsecproxy mit der Adresse des ServersNo subjectaltname matching addressradius.home.example benutzen

Warum ist das nicht standardmäßig in jedem Firmennetz?

Nichts in diesem Beitrag ist neu. EAP-TLS wurde im Oktober 1999 veröffentlicht37. Die VLAN-Attribute folgten im September 20033. Beide Switch-Hersteller oben dokumentieren dynamische VLANs in ihren eigenen Handbüchern, und die CA, der RADIUS-Server und der Proxy sind Open Source und kosten nichts. Geh den kommerziellen Weg, und dasselbe wird zu einem Posten auf der Rechnung: Cisco stellt „RADIUS authentication, authorization, and accounting, including 802.1X, MAC authentication bypass“ in die Essentials-Stufe von ISE, lizenziert nach „concurrent active endpoints“, wobei ein Endpunkt einer der „printers, fax machines, or other types of network devices“38 im Netz sein kann. So oder so steckt die Fähigkeit seit mehr als zwanzig Jahren in den Geräten.

Warum also läuft ein Firmennetz 2026 immer noch mit einem Passwort, das alle teilen?

Darf ich fragen, warum sich so viele Admins und MSPs einfach nicht die Mühe machen? Nicht, wer. Was es weiterlaufen lässt. Es gibt drei ehrliche Antworten, weil es drei Fälle gibt.

Manche wissen es nicht. Und das ist das Versagen, nicht die Ausrede. Zu wissen, was dein eigener Switch kann, ist nichts, was dir einfach passiert. Dafür zu sorgen, dass du es weißt, ist die Arbeit, und das Handbuch, das es sagt, ist ein kostenloser Download.

Manche wissen es und lassen es. Das gemeinsame Passwort funktioniert. Niemand macht ein Ticket deswegen auf. Ein Zertifikat pro Gerät ist ein Bündel Pflichten, bei dem nichts vorzuzeigen ist bis zu dem Tag, an dem ein Laptop verschwindet, und daher schafft es das nie auf die Liste. Für einen MSP ist die Rechnung noch einfacher: ein flaches Netz mit einem Passwort ist billiger zu betreuen, kein Vertrag misst es, und der Kunde fragt nie. Die MSP-Serie geht durch, wohin diese Anreize führen, und das hier ist dasselbe Muster bei einer kleineren Rechnung. Auf diesen Fall zielt die Frage: der Admin, der den Switch hat, der es kann, bis hierher gelesen hat und den gemeinsamen Schlüssel behält, weil ihn niemand zwingen wird, das zu rechtfertigen.

Manche können es wirklich noch nicht. Der Drucker ohne Supplicant, der Fernseher, der kein TLS 1.3 kann, die Fachanwendungs-Kiste, die ihr Hersteller nicht aktualisiert. Das ist eine echte Einschränkung, und die Antwort ist ja, das ist vertretbar: MAB in ein eingezäuntes VLAN, der Grund aufgeschrieben und ein Datum daneben, wann die Kiste geht.

Das Versagen ist nicht, eine Lücke zu haben. Jeder hat Lücken. Es ist, ein Firmennetz über eine Lücke hinweg zu betreiben, die man nie geschlossen hat, auf Geräten, die ab Werk in der Lage waren, sie zu schließen.

Jeder hatte einmal ein gemeinsames Passwort. Eins in einem Firmennetz aus freien Stücken zu behalten ist der Teil, der nicht vertretbar ist.

Warum Cyber Essentials es nicht verlangt

Weil es das nie sollte. Cyber Essentials ist die staatlich gestützte Grundlinie, und „a growing number of organisations require suppliers to be certified to bid for work“39. Es ist gegen „the most common internet based cyber security threats“39 gebaut, und seine Anforderungen sagen ausdrücklich, wo es aufhört. Funkgeräte sind außerhalb des Geltungsbereichs, wo ein Angreifer sie nicht aus dem Internet erreichen kann, weil „the Cyber Essentials scheme isn’t concerned with attacks that can only be launched within the signal range of the wireless device“40. Seine fünf Kontrollen sind Firewalls, sichere Konfiguration, Sicherheitsupdates, Zugriffskontrolle für Benutzer und Schutz vor Schadsoftware40. Keine davon fragt, wer überhaupt ins Netz darf. Am nächsten kommt es mit „change any default or guessable account passwords“40, und ein WLAN-Schlüssel, der auf dem Empfangstresen ausgedruckt liegt, ist kein Konto.

VLANs tauchen darin auf, einmal. Ein „sub-set“ einer Organisation, eins, das „segregated from the rest of the organisation by a firewall or VLAN“40 ist, kann für sich bewertet werden. Die einzige Stelle, an der das Programm einem VLAN begegnet, ist also als Mittel, die Grenze um weniger vom Netz zu ziehen.

Nach seinen eigenen Maßstäben ist das fair. Es ist ein Boden gegen Angriffe aus dem Internet, und es sagt das auch. Das Problem ist, was mit einem Boden passiert, sobald ein Zertifikat davon an der Wand hängt. Er wird zum Ziel, dann zur Decke, und ein Betrieb kann ihn bestehen, mit jedem Gerät in einem flachen Netz hinter einem Passwort, das die Putzkraft kennt. Jeder, der mit einem Laptop am Gebäude vorbeigeht, ist im Bedrohungsmodell, auf das es ankommt, und außerhalb dessen, das bewertet wurde.

Ein Passwort ist ein Versprechen, das niemand hält

Was mir beim Aufbau aufgefallen ist: wie wenig davon schwer war. Jede Falle darin stand irgendwo geschrieben, in einem Kommentar in der Konfiguration oder einem Herstellerhandbuch, und jede davon hat einem Nachmittag im Labor nachgegeben.

Und trotzdem läuft das Haus mit einem WLAN-Passwort, das auf dem Kühlschrank steht. Nicht weil die Alternative schwer ist, sondern weil das Passwort die Voreinstellung ist, und die Voreinstellung muss sich nie rechtfertigen. Alle wissen, dass es geteilt wird. Alle wissen, dass es leakt. Niemand ändert es, weil es zu ändern heißt, mit dem neuen zu jedem Gerät im Haus zu laufen, also bleibt es jahrelang dasselbe, über jeden Gast, jeden Untermieter und jedes Telefon hinweg, das mit dem noch gespeicherten Passwort im Recyclingbehälter gelandet ist, und es lässt jeden herein, der es je hatte.

Ein Zertifikat pro Gerät ist eine andere Vorstellung davon, wofür das Netz da ist. Das Netz sollte wissen, was ein Ding ist, nicht nur, dass jemand einmal das richtige Wort eingetippt hat. Das ist ein Standard, an den du dich hältst, ohne dass jemand prüft, und das ist die einzige Sorte, die etwas taugt. Die Kamera darf nicht neben dem Laptop sitzen, nur weil sie zufällig in derselben SSID war. Sie sitzt da, wo du sie hinsetzt, auf Geräten, die dir gehören, unter einer Regel, die du geschrieben hast und lesen kannst. Benimmt sie sich daneben, zieht sie mit einer Zeile in einer Textdatei um. Verschwindet sie, sperrt ein Befehl sie aus.

Der Tausch sind Pflichten. Eine CRL mit einem Timer dahinter, Zertifikate mit einem Termin im Kalender, Geräte, die getestet werden, bevor sie gekauft werden. Das ist der Preis, und so herum ist es richtig: du zahlst mit Mühe, die du sehen kannst, statt mit einem Passwort, das seit Jahren still und leise Leute hereinlässt.


  1. RFC 3748 — Extensible Authentication Protocol (EAP) — EAP und die Rollen Peer, Authenticator und Authentication Server, zwischen denen es läuft. ↩︎

  2. RFC 2868 — RADIUS Attributes for Tunnel Protocol Support — definiert die Tunnel-Attribute, die RFC 3580 für VLANs wiederverwendet. ↩︎

  3. RFC 3580 — IEEE 802.1X RADIUS Usage Guidelines — Abschnitt 3.31, „Tunnel-Type=VLAN (13) Tunnel-Medium-Type=802 Tunnel-Private-Group-ID=VLANID“, und „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 — gegenseitige Authentifizierung per Zertifikat über 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“; Abschnitt 2.1.7 empfiehlt anonyme NAIs in der Identity Response. ↩︎ ↩︎

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

  8. Smallstep — Certificate templates — mit --set übergebene Benutzerdaten stehen der Vorlage als .Insecure.User zur Verfügung; Werte immer in toJson einpacken. ↩︎

  9. FreeRADIUS 3.2.10 — dictionary.freeradius.internal — die Attribute TLS-Client-Cert-*, die die Richtlinie liest, darunter TLS-Client-Cert-Subject und 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)“, und domain_match, der „Constraint for server domain name“. ↩︎

  12. RFC 2548 — Microsoft Vendor-specific RADIUS Attributes — MS-MPPE-Send-Key und MS-MPPE-Recv-Key, verschlüsselt als „b(1) = MD5(S + R + A) c(1) = p(1) xor b(1)“, wobei S das gemeinsame Geheimnis ist. ↩︎

  13. BlastRADIUS — CVE-2024-3596; die empfohlene Abhilfe ist, „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 und limit_proxy_state; beide auf „no“ zu setzen „will allow MITM attackers to create fake Access-Accept packets to the NAS!“ ↩︎

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

  17. IETF — RadSec: RADIUS over TLS and DTLS — angestrebter Status Proposed Standard, ersetzt RFC 6614 und RFC 7360; IESG-Status „Has enough positions to pass“ beim Nachsehen am 13. Oktober 2026. ↩︎

  18. FreeRADIUS 3.2.10 — sites-available/tls — der RadSec-Listener, require_client_cert, proto = tls, und „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“, mit der neuesten Firmware für AP und Switch. ↩︎ ↩︎

  21. TP-Link Omada — 802.1X configuration — RADIUS Proxy: „It does not support the RadSec feature“; mit 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“; keine Erwähnung von RadSec. ↩︎

  23. Netgear — WAX630 user manual — „Enter the number of the UDP port on the access point that is used to access the RADIUS server“, und „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 mit 1.11.2 getestet. ↩︎

  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 ist „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.“ Das Handbuch des M4350 hat denselben Text. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  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“ im RADIUS-Profil. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  29. TP-Link — Configuration guide for 802.1X VLAN assignment and MAB — „VLAN Assignment takes effect only when the control type is Port Based“; fehlende VLANs werden automatisch angelegt; MAB schickt die MAC als „12 hexadecimal digits, all lowercase and no punctuation“. ↩︎ ↩︎ ↩︎ ↩︎

  30. Netgear — GS710TUP user manual — MAC-basierte Port-Steuerung „allows multiple supplicants connected to the same port“. ↩︎

  31. hostapd — hostapd.conf — dynamisches VLAN, „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 und vlan_bridge. Aus dem Paket hostapd 2.11 gelesen. ↩︎

  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 steht standardmäßig auf 1.2. ↩︎ ↩︎ ↩︎

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

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

  37. RFC 2716 — PPP EAP TLS Authentication Protocol — die ursprüngliche EAP-TLS-Spezifikation, Oktober 1999, inzwischen durch RFC 5216 ersetzt. ↩︎

  38. Cisco — ISE licences — Essentials umfasst „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“; es schützt gegen „the most common internet based cyber security threats“. ↩︎ ↩︎

  40. NCSC — Cyber Essentials: Requirements for IT Infrastructure v3.3 — April 2026. Funkgeräte sind außerhalb des Geltungsbereichs, wo sie nicht aus dem Internet angegriffen werden können: „the Cyber Essentials scheme isn’t concerned with attacks that can only be launched within the signal range of the wireless device“; die fünf technischen Kontrollen; „change any default or guessable account passwords“; ein Sub-Set „is part of the organisation whose network is segregated from the rest of the organisation by a firewall or VLAN“. ↩︎ ↩︎ ↩︎ ↩︎