Un seul mot de passe pour toute la maison

Un mot de passe WiFi est partagé par tout ce qui s’est jamais connecté. Les portables, les téléphones, la télé, la caméra de la sonnette dont personne n’a mis à jour le firmware depuis la pose, l’invité à qui on l’a donné à Noël, et chaque téléphone que cet invité a eu depuis. Tous sont sur le même réseau. Chacun peut parler à chacun des autres.

Découper tout ça de la façon habituelle veut dire plus de SSID. Un pour le personnel, un pour l’IoT, un pour les invités, chacun avec son mot de passe et son VLAN. Ça marche. Puis un mot de passe fuit, et vous faites le tour de chaque appareil de ce SSID avec un nouveau, ce qui est exactement le travail qui ne se fait jamais.

Un port filaire libre, c’est pire. Aucun mot de passe du tout. Branchez-vous et vous êtes sur le VLAN auquel le port a été réglé.

Le 802.1X avec des certificats renverse ça. Chaque appareil porte son propre certificat et prouve ce qu’il est avant qu’un seul paquet ne passe, et ensuite c’est le réseau, ni l’appareil ni celui qui a distribué le mot de passe, qui décide où il va. Un seul SSID. Tous les ports de switch identiques. Le portable atterrit sur le VLAN du personnel, la caméra sur le VLAN IoT, et un appareil auquel vous n’avez jamais émis de certificat n’atterrit nulle part.

Un mot de passe partagéUn SSID par VLAN802.1X, un seul SSID
Qui entreQuiconque a le mot de passeQuiconque a le mot de passe de ce SSIDSeulement un appareil détenant un certificat que vous avez émis
Quel VLANTous sur un seulCelui du SSID qu’ils ont rejointCe que le serveur RADIUS dit pour cet appareil
Bloquer un appareil perduChanger le mot de passe partoutChanger le mot de passe de ce SSID partoutRévoquer un certificat
Ports filairesOuvertsOuvertsMême vérification que le WiFi
SSID à diffuser1Un par VLAN1

C’est ce que construit cet article. step-ca émet les certificats et y écrit le rôle de chaque appareil, FreeRADIUS vérifie chaque certificat contre l’AC et sa liste de révocation, en lit le rôle et répond avec un VLAN, et le switch ou le point d’accès place l’appareil là où on le lui a dit. Le matériel est du Netgear ou du TP-Link, qui parle à FreeRADIUS en TLS quand il le peut. Tout a tourné ici avant d’approcher du vrai matériel : l’AC et le serveur RADIUS dans des conteneurs, le port de switch et le réseau WiFi dans des simulateurs.

Quatre pièces, et une seule décide

Le 802.1X nomme trois rôles1. Le supplicant est l’appareil qui demande à entrer. L’authentificateur est le port de switch ou le point d’accès. Le serveur d’authentification est RADIUS. Ajoutez l’AC qui a émis les certificats et c’est tout le système.

Qui fait quoi dans une connexion 802.1XAppareilsupplicantSwitch ou borneauthentificateurrelaie, ne décide rienFreeRADIUSchoisit le VLANstep-cacertificats et CRLEAP-TLSle port s'ouvreRadSecVLAN 10CRL, HTTPScertificat, émis une fois
Le switch ou le point d’accès ne fait que relayer. EAP voyage de l’appareil à FreeRADIUS dans EAPOL puis dans RADIUS, et la réponse revient avec le VLAN dans trois attributs. step-ca ne voit jamais une connexion ; il a signé les certificats au préalable et publie la liste de révocation.

Le switch et le point d’accès ne décident rien. Ils relaient. Tant que l’appareil ne s’est pas authentifié, le port laisse passer EAP et rien d’autre. L’authentificateur enveloppe cet EAP dans RADIUS, l’envoie au serveur, et agit selon la réponse. Un Access-Accept ouvre le port. Si l’Accept porte des attributs de tunnel, le port ou le client WiFi passe sur le VLAN qu’ils nomment. La RFC 3580 fixe quels sont ces trois attributs, empruntés aux attributs de tunnel de RADIUS23 :

AttributValeurSignification
Tunnel-TypeVLAN (13)Ce tunnel est un VLAN
Tunnel-Medium-TypeIEEE-802 (6)Sur un réseau 802
Tunnel-Private-Group-Id"10"L’identifiant de VLAN, envoyé comme chaîne

La marque du switch compte donc moins qu’on ne le croirait. La décision vit sur une machine que vous faites tourner. Chaque fabricant qui fait du VLAN dynamique lit les trois mêmes attributs. La RFC dit clairement que l’identifiant de VLAN est « encoded as a string »3, et c’est pourquoi il est entre guillemets.

Pourquoi des certificats, et pourquoi TLS 1.3 seulement

Il existe plusieurs méthodes EAP, et la plupart de celles en usage sont un mot de passe enveloppé dans du TLS. Ça marche pour une personne. C’est inutile pour une caméra, qui n’a personne pour taper quoi que ce soit, et un mot de passe partagé par mille caméras, c’est de nouveau le mot de passe WiFi.

MéthodeCe qui prouve l’appareilSi l’appareil fait confiance au mauvais serveurBloquer un appareil
EAP-TLSSon propre certificat et sa clé privéeLe serveur apprend le certificat, qui est public de toute façonRévoquer le certificat
PEAP avec MS-CHAPv2Un nom d’utilisateur et un mot de passeLe serveur capture une poignée de main qu’on peut casser jusqu’au mot de passeChanger le mot de passe
EAP-TTLS avec PAPUn nom d’utilisateur et un mot de passeLe serveur reçoit le mot de passe lui-mêmeChanger le mot de passe

C’est cette colonne du milieu qui justifie EAP-TLS. MS-CHAPv2 est cassé depuis plus de dix ans. L’avis de Microsoft lui-même dit que « an attacker who obtained the MS-CHAP v2 authentication traffic could then use the exploit code to decrypt a user’s credentials »4. L’envelopper dans PEAP ne le protège que si chaque appareil vérifie à qui il parle, et comme le montre la section sur les appareils, bon nombre ne le font pas. Avec EAP-TLS, il n’y a aucun secret à voler dans l’échange. La clé privée ne quitte jamais l’appareil5. Rien à casser.

TLS 1.2 met le certificat sur les ondes

EAP-TLS sur TLS 1.2 a un défaut facile à rater. L’appareil envoie son certificat avant que le chiffrement ne commence. En clair. Quiconque écoute le WiFi peut le lire, et puisque cet article écrit le rôle de l’appareil dans le certificat, il peut lire ça aussi.

Mesuré ici, en cherchant dans chaque message EAP envoyé par l’appareil le nom de l’appareil et son rôle :

ExécutionTLSNom de l’appareil en clairRôle OU=staff en clair
Identité laptop11.2OuiOui
Identité laptop11.3Oui, dans l’identitéNon
Identité anonymous1.3NonNon

TLS 1.3 corrige ça. La RFC 9190 dit qu’avec TLS 1.3 « the use of privacy is mandatory and does not cause any additional round trips »6. L’identité externe passe toujours en clair, donc elle ne doit pas non plus nommer l’appareil. La RFC recommande une identité anonyme6. Rien ici n’a besoin d’une vraie, parce que FreeRADIUS lit qui est l’appareil dans le certificat. Avec anonymous à l’extérieur, la dernière ligne est ce qu’obtient un espion : rien.

Ce montage tourne donc en TLS 1.3 seulement. FreeRADIUS y est épinglé aux deux bouts. Un appareil qui ne le parle pas n’entre pas. Ça a un coût, et il est compté dans la section sur les appareils plutôt que caché. La configuration de FreeRADIUS elle-même vous dit encore de plafonner à 1.2, en majuscules, parce que « most EAP supplicants WILL NOT DO TLS 1.3 PROPERLY »7. Rendons à César ce qui lui appartient : cette configuration est la documentation la plus honnête de tout cet article, et presque chaque piège ci-dessous y est écrit quelque part si vous lisez les commentaires. Sur un réseau où vous choisissez les appareils, c’est une raison de choisir de meilleurs appareils.

Mettre le rôle dans le certificat

Le VLAN est décidé par ce qu’est l’appareil, et c’est dans le certificat que ça va. step-ca peut l’y écrire avec un modèle, le même mécanisme que l’article sur les certificats clients utilise pour le point de distribution de la CRL. Le démarrage de l’AC elle-même est couvert dans step-ca dans un conteneur, et ceci reprend à partir de là avec une AC nommée Home Network CA.

Chaque certificat d’appareil reçoit son rôle dans l’unité organisationnelle (OU) de son sujet. Le modèle prend le rôle dans la requête et refuse tout ce qui n’est pas sur la liste8 :

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

Donnez-lui un provisioner à lui, pour que les appareils ne sortent jamais du provisioner admin à tout faire, avec un an comme certificat le plus long qu’il signera :

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

--x509-template copie le modèle dans ca.json au lieu de pointer sur le fichier, donc modifier device.tpl ensuite ne change rien tant que vous ne relancez pas step ca provisioner update network --x509-template templates/device.tpl. Émettez ensuite le certificat de chaque appareil avec son rôle :

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

Un rôle absent ou inconnu n’obtient rien :

$ 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 s’appelle insecure parce que c’est ce qu’envoie le demandeur, quoi que ce soit. Ici, ça va. Le provisioner network a un mot de passe, vous seul le détenez, et quiconque peut signer avec peut déjà fabriquer n’importe quel certificat, donc un rôle qu’il aurait tapé lui-même ne lui donne rien de plus.

Le serveur RADIUS a lui aussi besoin d’un certificat, pour que les appareils puissent vérifier à qui ils parlent. Il vient du provisioner admin comme n’importe quel autre certificat serveur, et vit les 24 heures par défaut, renouvelé par step ca renew --daemon comme l’article step-ca le fait pour tout service qui ne sait pas faire d’ACME. Les appareils vérifient le nom et l’AC, pas le numéro de série. Un renouvellement quotidien ne change rien pour eux.

CertificatProvisionerDurée de vieUsage de clé étenduPorte
radius.home.exampleadmin24 heures, renouvelé chaque jourServeur et clientLe nom que vérifient les appareils
laptop1network1 anClient seulementOU=staff
cam1network1 anClient seulementOU=iot
tv1network1 anClient seulementOU=media
switch1, ap1nas1 anClient seulementOU=nas, pour RadSec ; voir plus bas

FreeRADIUS : décider du VLAN

FreeRADIUS 3.2.10, dans l’image de conteneur du projet lui-même. Trois morceaux de configuration font le travail : le module EAP, une politique qui choisit le VLAN, et une liste d’exceptions.

Le module EAP

Seulement EAP-TLS, seulement TLS 1.3, et la CRL vérifiée à chaque connexion :

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
	}
}
FichierContient
radius.crtLe certificat serveur, puis l’intermédiaire de step-ca, tel que l’écrit step ca certificate
radius.keySa clé privée ; comme tout ici, lisible par l’utilisateur freerad sinon FreeRADIUS ne démarre pas
ca/La racine, l’intermédiaire et la CRL de l’intermédiaire, hachés avec openssl rehash ca/

Cette ligne du milieu a bloqué le premier démarrage ici, mais pas pour la clé : Permission denied sur le certificat racine, parce que chaque fichier était sorti de l’AC lisible par son seul propriétaire. FreeRADIUS tourne en freerad, donc les trois doivent être lisibles par lui.

check_crl = yes vérifie le certificat de l’appareil contre la CRL de l’intermédiaire. Il ne vérifie pas l’intermédiaire lui-même, donc contrairement à nginx dans l’article sur les certificats clients, FreeRADIUS n’a pas besoin de CRL racine. check_all_crl l’active si vous le voulez7. ca_path_reload_interval compte plus qu’il n’y paraît, et a sa propre section plus bas.

La politique

Chaque certificat que cette AC a jamais émis passerait la vérification TLS. Y compris ceux des serveurs web obtenus par ACME. La politique jette donc tout ce qui n’a pas été fait pour ce travail : pas d’usage d’authentification client, ou pas de rôle, et c’est rejeté.

Comment FreeRADIUS choisit le VLANPoignée de main TLS 1.3, CRLUsage client-auth ?Sur la liste ?Rôle dans le certificat ?Pas de rôle : rejetRejetLa liste gagne : cam1, VLAN 99staff, 10iot, 20media, 30ouinonaucunnonoui
La liste par appareil est consultée en premier et l’emporte quand elle a une réponse. Sinon, le rôle dans le certificat choisit le VLAN. Un certificat qui n’a ni l’un ni l’autre est refusé, et c’est ce qui empêche les certificats de serveur web de l’AC d’entrer sur le réseau.
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
	}
}

Ça va dans policy.d/vlan, et une ligne, vlan_by_certificate, va en tête de la section post-auth de sites-available/default. Le post-auth est l’endroit où les détails du certificat sont disponibles, une fois la poignée de main TLS terminée9.

La liste d’exceptions

Le rôle décide de la plupart des appareils. Pas de tous. La liste sert aux quelques-uns pour lesquels il ne devrait pas : une caméra qui s’est mise à se comporter bizarrement et qu’on veut mettre en quarantaine avant de comprendre pourquoi, ou un appareil prêté pour une semaine. C’est un module passwd qui lit un fichier à deux colonnes, indexé sur le nom commun du certificat :

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

Le = devant l’attribut le place dans la réponse plutôt que dans les éléments de vérification10. Déplacer un appareil, c’est une ligne dans un fichier texte, sans certificat à réémettre et sans rien à toucher sur l’appareil.

VLANRôleCe qui y vit
10staffLes portables et les téléphones de confiance
20iotCaméras, capteurs, tout ce qui appelle la maison mère
30mediaTélés et boîtiers de streaming
99Liste seulementQuarantaine : internet au mieux, rien en local

Le prouver avant de toucher un switch

eapol_test est livré avec wpa_supplicant et joue à la fois un appareil et un switch, en parlant directement au serveur RADIUS. Servez-vous-en d’abord. Quand quelque chose ne marchera pas plus tard, vous saurez quelle moitié regarder.

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>

Les réponses, tirées du journal de débogage de FreeRADIUS lui-même :

(8) Sent Access-Accept
(8)   Tunnel-Private-Group-Id = "10"
(8)   Tunnel-Type = VLAN
(8)   Tunnel-Medium-Type = IEEE-802
AppareilRôle dans le certificatSur la listeRéponseVLAN
laptop1staffNonAccept10
cam1iot99Accept99
tv1mediaNonAccept30
laptop2staff, révoquéNonReject, certificate revokedAucun
radius.home.exampleAucunNonReject, pas de rôleAucun
laptop1 avec TLS 1.3 laissé désactivéstaffNonReject, protocol versionAucun

Cette dernière ligne vaut d’être connue avant la mise en service. wpa_supplicant 2.11 est encore livré avec TLS 1.3 désactivé pour EAP, décrit comme « experimental - disabled by default »11. Oubliez la ligne phase1 et un portable parfaitement valable est refusé.

Un port de switch, simulé

eapol_test saute le switch. Pour voir la moitié du switch, hostapd a un pilote filaire qui transforme une interface Linux en port 802.1X. Un câble virtuel le relie à un wpa_supplicant qui joue le portable, et il pointe sur le même 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>

Ce que voit le port d’un bon portable et d’un révoqué :

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

Activez sa gestion des VLAN dynamiques et il annonce le VLAN qu’on lui a donné, vlan=10 pour le portable et vlan=99 pour la caméra en quarantaine, puis dit could not add dynamic VLAN interface. C’est un port Linux qui est honnête : il sait s’ouvrir et se fermer, mais il ne sait pas se déplacer lui-même sur un VLAN. Un switch administrable fait cette partie en matériel, et c’est la moitié que couvrent les réglages des fabricants plus bas.

La révocation : la CRL que FreeRADIUS ignore sur un HUP

Un portable perdu est le cas pour lequel tout ça existe. Révoquez-le à l’AC. Le provisioner admin signe un jeton de révocation pour le numéro de série, ce qui marche sans la clé du portable, parce que vous n’aurez pas le portable :

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 reconstruit sa CRL immédiatement quand generateOnRevoke est activé, réglé comme le montre l’article sur les certificats clients. Copiez-la dans le répertoire ca/ de FreeRADIUS et le portable est dehors. Sauf qu’il ne l’est pas. Il est revenu tout droit.

Ce qui a été fait après l’arrivée de la nouvelle CRL sur le disquePortable révoqué
RienEntre toujours
kill -HUPEntre toujours. Le journal dit HUP - No files changed. Ignoring
Attendu au-delà de ca_path_reload_intervalRefusé, certificate revoked
Redémarré FreeRADIUSRefusé, certificate revoked

Un HUP recharge les fichiers de configuration, et une CRL dans ca_path n’en fait pas partie, donc FreeRADIUS vérifie sa liste de fichiers de configuration, n’en trouve aucun de modifié, et continue avec l’ancienne CRL en mémoire. Ce qui la prend en compte, c’est ca_path_reload_interval. Il est désactivé dans la configuration par défaut. Réglez-le sous 300 secondes et FreeRADIUS passe outre au démarrage :

ca_path_reload_interval is set too low, reset it to 300

Ce sera donc 300. Une révocation prend effet dans les cinq minutes qui suivent l’arrivée de la nouvelle CRL. Récupérez-la sur minuterie, comme l’article sur les certificats clients le fait déjà pour nginx, mais dans ce répertoire-ci. Redémarrez FreeRADIUS si cinq minutes, c’est trop long pour l’appareil que vous venez de perdre.

La CRL peut aussi bloquer tout le monde d’un coup. Dans les deux sens, donc. Les deux cas ont été testés avec de bons certificats :

État de la CRLCe que dit FreeRADIUSQui entre
À jourRienTous les bons appareils
Absente de ca/error 3 : unable to get certificate CRLPersonne
Au-delà de son heure de mise à jourerror 12 : CRL has expiredPersonne

La CRL de step-ca dure 24 heures par défaut. Si la minuterie qui la récupère s’arrête, chaque appareil du réseau échoue le lendemain. allow_expired_crl existe pour adoucir ça7. Ne l’utilisez pas. Un réseau qui laisse entrer tout le monde parce qu’une liste est périmée a cessé de vérifier quoi que ce soit, et en tant que tel le correctif consiste à surveiller la minuterie, pas à couper la vérification.

RADIUS, c’est toujours du MD5 et un secret partagé

Tout ce qui précède protège la conversation entre l’appareil et FreeRADIUS. EAP-TLS tourne de bout en bout, et le switch ou le point d’accès au milieu ne peut pas la lire. Le paquet RADIUS qui la transporte, c’est une autre affaire.

Le RADIUS classique, c’est de l’UDP avec un secret partagé, et le secret est utilisé via MD5, ce qui compterait beaucoup moins si le paquet ne transportait rien de valeur, mais l’Access-Accept d’un client WiFi transporte les clés du trafic de ce client. MS-MPPE-Recv-Key et MS-MPPE-Send-Key sont la clé maîtresse que le point d’accès utilise pour chiffrer le trafic de ce client, et la RFC 2548 les protège avec b(1) = MD5(S + R + A) et un XOR, où S est le secret partagé12. Il n’y a aucune confidentialité persistante là-dedans. Capturez le trafic RADIUS aujourd’hui, obtenez le secret l’an prochain depuis une sauvegarde de configuration ou la configuration en cours d’un switch, et chaque clé capturée se déchiffre, avec le trafic WiFi qu’elle protégeait.

La question n’est donc pas de savoir si le secret est assez long. C’est de savoir pourquoi les clés du réseau sans fil traversent le câble sous MD5 tout court. Elles n’ont pas à le faire.

MenaceRADIUS simple sur UDPAvec Message-Authenticator exigéRadSec, TLS 1.3, certificats mutuels
Access-Accept falsifié (BlastRADIUS)Possible sur les requêtes sans EAP, MAB comprisBloquéBloqué
Clés WiFi lues depuis une captureQuiconque apprend un jour le secretPareilExige les clés de session TLS, qui ne sont jamais stockées
Une machine sur le réseau d’administration se faisant passer pour un switchN’a besoin que du secretPareilExige un certificat au rôle nas, depuis une adresse connue
Qui voit quel appareil s’est connectéQuiconque est sur le cheminPareilPersonne

BlastRADIUS, et la ligne qui l’arrête

BlastRADIUS, CVE-2024-3596, permet à un attaquant placé entre le switch et le serveur de transformer un refus en acceptation en attaquant le MD5 qui signe la réponse13. Le correctif est l’attribut Message-Authenticator sur chaque requête et chaque réponse13. La RFC 3579 l’a toujours exigé sur tout ce qui transporte de l’EAP14, donc EAP-TLS n’a jamais été le point faible. MAB l’était, parce qu’une vérification d’adresse MAC ne transporte pas d’EAP et que bien des switchs l’envoyaient sans.

FreeRADIUS 3.2.10 l’exige d’origine, avec require_message_authenticator = yes dans radiusd.conf. Testé ici avec une requête MAB construite à la main :

RequêteRéponse
Sans Message-AuthenticatorAucune réponse. Journalisé comme Packet does not contain required Message-Authenticator attribute
Avec Message-AuthenticatorUne réponse, Access-Reject pour une MAC inconnue

Laissez ce réglage tel quel. La configuration prévient que le mettre, avec limit_proxy_state, à no « will allow MITM attackers to create fake Access-Accept packets to the NAS »15.

Ça ferme la falsification. Ça ne fait rien pour les clés.

RadSec : RADIUS dans TLS 1.3

Le plus haut niveau disponible est RadSec, du RADIUS transporté dans TLS avec un certificat à chaque bout16. Le switch ou le point d’accès prouve qui il est avec un certificat, FreeRADIUS prouve qui il est avec le sien, et toute la conversation RADIUS, clés comprises, se trouve dans TLS 1.3 avec confidentialité persistante. L’IETF est en train de le faire passer sur la voie des normes ; le brouillon qui remplacerait la RFC expérimentale de 2012 en est à « Has enough positions to pass »17.

Les switchs et les points d’accès obtiennent des certificats de step-ca comme tout le reste, d’un troisième provisioner dont le modèle inscrit OU=nas et rien d’autre, ce qui veut dire qu’un certificat d’appareil ne peut pas servir à se faire passer pour un switch, et qu’un certificat de switch, qui ne porte aucun rôle reconnu par la politique de VLAN, ne peut pas non plus servir à entrer sur le réseau.

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

Le secret est vraiment le mot radsec. Si, si. Sur TLS, il ne sert à rien, et l’exemple de FreeRADIUS lui-même dit « TLS clients MUST have the shared secret set to “radsec” »18. Deux choses qui paraissent naturelles ne marchent pas, toutes deux découvertes à la dure. check_cert_cn = %{client:shortname} ne s’étend à rien au moment de la poignée de main, donc chaque switch est refusé avec Certificate CN (switch1) does not match specified value () ; d’où le script verify. Et l’écouteur ne démarre pas sous freeradius -X, qui désactive les threads : Threading must be enabled for TLS sockets to function properly. Déboguez-le plutôt avec freeradius -fxx -l stdout.

Protéger chaque saut vers FreeRADIUSFreeRADIUSport 2083OU=nas seulementOmada, hostapdRadSec intégréRadSec, TLS 1.3NetgearUDP seulementradsecproxymême VLAN adminUDP+ Msg-AuthRadSecstep-caémet chaque certificatCRL en HTTPS, racine épinglée
Où chaque saut est protégé. Le matériel qui parle RadSec va droit à FreeRADIUS en TLS 1.3. Le matériel qui ne parle qu’UDP s’adresse à radsecproxy sur le même VLAN d’administration, et seul ce court saut est du RADIUS simple. La CRL vient de step-ca en HTTPS avec la racine épinglée.

Le port de switch simulé de tout à l’heure, reconstruit pour utiliser RadSec, face aux mêmes tests :

Le switch présenteDepuisRésultat
switch1, OU=nasSon adresse listéeTLS 1.3 établi ; laptop1 autorisé sur le VLAN 10, laptop2 révoqué refusé
Le certificat de laptop1L’adresse du switchRefusé pendant la poignée de main ; le script verify renvoie 1
switch1, OU=nasUne adresse non listéeIgnoring request … from unknown client

Quel matériel sait le faire

AuthentificateurRadSecRemarques
hostapd 2.11Oui, s’il est compilé avecCONFIG_RADIUS_TLS=y est commenté dans son defconfig19. Le paquet Alpine le laisse désactivé et refuse la configuration : unsupported RADIUS type 'TLS'. Compilé avec, il a marché du premier coup
Points d’accès et switchs TP-Link OmadaÀ partir du Controller 6.1, avec un firmware à jour« Added support for RadSec (RADIUS over TLS) to secure SSID, MAC-Based, and 802.1X authentications on both APs and Switches »20
Proxy RADIUS de TP-Link OmadaNonLe proxy du contrôleur lui-même « does not support the RadSec feature »21
Netgear M4250 et M4350Non documentéLe manuel CLI ne mentionne pas RadSec, et plafonne le secret partagé : « The secret must be an alphanumeric value not exceeding 16 characters »22
Points d’accès Netgear WAX et WBENon documentéLes manuels décrivent le port RADIUS comme UDP, et le secret par défaut comme sharedsecret23

Puis-je demander pourquoi un switch vendu comme entièrement administrable en 2026 ne sait pas parler à son serveur RADIUS en TLS ? Pas qui l’a décidé. Ce qui, dans le processus, l’a laissé sortir ainsi, alors que le contrôleur de TP-Link, hostapd et un proxy gratuit en sont tous capables. Seize caractères alphanumériques aléatoires, c’est environ 95 bits, et ce n’est pas ça qui se fait casser. Le problème, c’est que c’est la seule protection proposée : aucune option TLS, un schéma MD5 de 1999 qui garde les clés WiFi, et un point d’accès livré avec le secret réglé sur le mot sharedsecret. Sur du matériel Netgear, radsecproxy n’est pas facultatif.

radsecproxy, pour le matériel qui ne parle qu’UDP

Là où le switch ou le point d’accès ne sait pas faire de RadSec, placez radsecproxy à côté. Il prend du RADIUS UDP en entrée et envoie du RadSec en sortie24, donc le saut en clair, ce sont les quelques mètres de VLAN d’administration entre le switch et le proxy, pas tout le chemin jusqu’au serveur :

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
}

Utilisez le nom. Pas l’adresse. Avec host 10.89.0.100, le proxy a refusé le serveur, à juste titre : No subjectaltname matching address 10.89.0.100. Avec le nom, il est monté en TLSv1.3 with cipher TLS_AES_256_GCM_SHA384, et les mêmes appareils ont obtenu les mêmes réponses à travers lui : laptop1 VLAN 10, cam1 99, tv1 30, laptop2 rejeté. Une requête sans Message-Authenticator s’est arrêtée au proxy : ignoring request from client switches … missing required message-authenticator.

Gardez ce saut UDP honnête. Court, et clôturé. L’adresse d’administration du switch et le proxy se trouvent sur un VLAN d’administration vers lequel aucun VLAN client ne peut router, et le pare-feu le confirme.

La récupération de la CRL aussi

La CRL est signée, donc personne ne peut en falsifier une. On peut en revanche en rejouer une plus ancienne encore valide, ce qui masque une révocation faite depuis. Récupérez-la auprès de step-ca en HTTPS avec sa propre racine épinglée, plutôt qu’en HTTP simple :

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

Face à toute autre racine, la même commande échoue avec la sortie 60 de curl, et FreeRADIUS garde la liste qu’il a.

Où ça va

La RFC 9765, RADIUS/1.1, retire entièrement MD5 de RADIUS quand il tourne sur TLS : « the RADIUS shared secret is no longer used, and all MD5-based packet authentication and attribute obfuscation methods are removed »25. Elle est expérimentale. FreeRADIUS 3.2 l’a derrière une option de compilation, --with-radiusv11, « for testing interoperability »26, et l’image officielle 3.2.10 n’est pas compilée avec ; l’option n’est pas dans le binaire. RadSec avec TLS 1.3 est le plus haut niveau que vous puissiez déployer aujourd’hui, et RADIUS/1.1 est l’étape suivante.

Tout switch administrable digne de ce nom fait du 802.1X avec des VLAN dynamiques, et les réglages se résument aux cinq mêmes choses quel que soit le logo. La différence entre ces deux-là est dans le détail, et un détail côté TP-Link change la façon de câbler un bureau.

RéglageNetgear M4250 et M4350Switchs TP-Link Omada
Serveur RADIUSSecurity > Management Security > RADIUS > Server Configuration27Un RADIUS Profile dans le contrôleur Omada28
802.1X activéSecurity > Port Authentication > Basic > 802.1X Configuration : Dot1x Admin Mode, Authentication Admin Mode27Authentication > 802.1X, Authentication Type « Port Based »28
VLAN depuis RADIUSVLAN Assignment Mode, « The default is Disable »27« enable ‘VLAN Assignment’ »28
VLAN manquantPas créé sauf si Dynamic VLAN Creation Mode est activé, lui aussi désactivé par défaut27« the switch will create the related VLAN automatically »29
Ports d’accèsControl Mode Auto802.1X Control mode Auto28
Liaisons montantes, port du serveur RADIUS, ports des points d’accèsForce Authorized802.1X laissé désactivé : « Keep 802.1X authentication disabled on ports connected to the authentication server, the internet and the management computer »29

Créez d’abord les VLAN sur le switch, dans les deux cas. Un VLAN qui existe parce qu’une connexion l’a fait apparaître est un VLAN que personne n’a configuré, et sur lequel personne n’a mis de règle de pare-feu.

Le guide de TP-Link lui-même dit « VLAN Assignment takes effect only when the control type is Port Based »29. Port Based veut dire que le premier appareil qui s’authentifie fixe le VLAN pour tout le port. Mettez sur ce port un switch de bureau ou un téléphone avec un PC branché derrière, et le second appareil entre sur le VLAN du premier sans qu’on lui ait jamais demandé de certificat, ce qui, sur un port du personnel, veut dire que tout ce qui est branché à l’arrière de ce téléphone est sur le VLAN du personnel.

Donc chez TP-Link : un appareil par port d’accès. Rien entre la prise murale et l’appareil. Le mode MAC-based de Netgear authentifie séparément chaque appareil d’un port ; le manuel du GS710TUP le décrit comme permettant « multiple supplicants connected to the same port »30.

MAB, pour l’imprimante qui ne sait rien faire de tout ça

Certains matériels n’ont aucun client 802.1X. Le MAC Authentication Bypass permet au switch d’envoyer l’adresse MAC de l’appareil à RADIUS à la fois comme nom d’utilisateur et comme mot de passe, et les deux fabricants le prennent en charge avec le 802.1X sur le même port. Netgear essaie d’abord le 802.1X et se rabat ensuite : « if a client tries 802.1X authentication… and authentication fails, the interface proceeds with MAB authentication »27. TP-Link envoie la MAC sous forme de « 12 hexadecimal digits, all lowercase and no punctuation »29.

Une adresse MAC n’est pas une identité. N’importe qui peut régler sa carte réseau sur la MAC de l’imprimante en une commande, depuis un portable qu’il a apporté, debout à côté de l’imprimante dont il vient d’emprunter le câble. MAB a donc sa propre liste, et la politique ne lui donnera jamais que le VLAN IoT ou la 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
		}
	}
}

Ça va en premier dans authorize, avec un module passwd appelé mab_vlan qui lit des lignes mac,vlan exactement comme la liste des appareils. La politique des certificats est enveloppée dans if (&EAP-Message) pour laisser MAB tranquille. Testé :

Requête MABEntrée de la listeRéponse
aabbccddeeff20Accept, VLAN 20
112233445566AucuneReject
ccddeeff001110, le VLAN du personnelReject. La politique ne mettra pas une MAC sur le VLAN du personnel, quoi que dise la liste

Celui qui usurpe l’imprimante obtient le VLAN de l’imprimante et rien de plus. C’est le maximum qu’une adresse MAC devrait jamais acheter.

Les réglages de switch Omada ci-dessus ont aussi été vérifiés contre un Omada Software Controller simulé, 6.2.14.11, dans un conteneur sans aucun matériel derrière. Ses chaînes d’interface en anglais nomment chaque réglage utilisé ici : l’option RadSec, où « 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 », et l’import du certificat et de la clé client pour RadSec. Un contrôleur configure ; il ne fait passer aucun trafic, donc ceci prouve que les réglages existent dans cette version et rien de plus. Netgear n’a rien d’équivalent à faire tourner, donc sa colonne vient des propres manuels de Netgear et n’a pas été testée ici.

Le point d’accès : un SSID, tous les VLAN

C’est au point d’accès que le SSID unique rapporte. Un seul réseau WPA-Enterprise, et chaque client est placé sur le VLAN que RADIUS nomme pour lui. Le point d’accès étiquette le trafic de ce client avec son VLAN et l’envoie dans le câble, donc le port de switch qui alimente le point d’accès doit transporter, étiqueté, chaque VLAN que RADIUS pourrait distribuer. Oubliez-en un et l’appareil s’authentifie parfaitement et n’obtient jamais d’adresse.

Un SSID, simulé

Avant de croire l’un ou l’autre fabricant sur parole, le tout a tourné sur des radios simulées. mac80211_hwsim de Linux fabrique des cartes WiFi virtuelles qui font du vrai WPA-Enterprise. Le noyau de cette machine ne livre pas le module, donc le test a démarré le même noyau dans une petite machine virtuelle KVM et l’y a chargé, depuis le paquet signé de Fedora lui-même, avec hostapd 2.11 comme point d’accès et trois stations wpa_supplicant dans leurs propres espaces de noms réseau31 :

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 veut dire « exigé » : une connexion sans VLAN dans la réponse est refusée plutôt que déposée sur un défaut. Chaque station s’est connectée en anonymous sur TLS 1.3, et le journal du point d’accès lui-même montre ce que RADIUS lui a dit :

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)
Un SSID, trois VLANlaptop1OU=staffcam1sur la listetv1OU=mediaPoint d'accèsSSID « Home »VLAN 10, clé AVLAN 99, clé BVLAN 30, clé CSwitchport trunk10, 30, 99 étiquetésun seul câble
Un SSID, trois appareils, trois VLAN. Le point d’accès place chaque client sur sa propre interface VLAN avec sa propre clé de groupe, et envoie les trois dans un seul trunk étiqueté. Le port de switch qui alimente le point d’accès doit transporter chaque VLAN que RADIUS peut nommer.
StationCertificatVLANPontSa propre passerelleLe VLAN de la télé
laptop1OU=staff10brvlan10JoignablePas de réponse
cam1OU=iot, sur la liste99brvlan99Joignable
tv1OU=media30brvlan30Joignable

Les lignes de clé de groupe sont la partie que les gens ratent. Les trames de diffusion et de multidiffusion sur le WiFi sont chiffrées avec une clé de groupe partagée par tout le monde sur le réseau, et si les trois VLAN en partageaient une, la caméra pourrait lire les diffusions du portable sur les ondes, quoi qu’en pense le switch. hostapd garde une clé de groupe séparée par VLAN, et le journal le montre en démarrer une pour chacun. La même exécution en WPA3-Enterprise, WPA-EAP-SHA256 avec protection des trames de gestion exigée, a donné les trois mêmes VLAN et les trois mêmes clés de groupe.

RéglageNetgear WAX et WBETP-Link Omada EAP
SécuritéWPA2 Enterprise ou WPA3 EnterpriseWPA2-Enterprise ou WPA3-Enterprise, dont un mode exclusif avec CCMP-128
VLAN depuis RADIUSDynamic VLAN, Enable23« Enable VLAN Assignment for Wireless Network »28
Pas de VLAN dans la réponseLe client va sur le VLAN propre du SSID23Non documenté ; FreeRADIUS ici n’envoie jamais d’Accept sans
Transport RADIUSUDP seulement, donc radsecproxyRadSec à partir du Controller 6.1 avec un firmware à jour20

Deux détails Netgear valent d’être connus avant qu’ils ne mordent. Un client auquel le serveur RADIUS ne donne aucun VLAN est « automatically assigned the VLAN ID that you configured for the SSID »23, donc réglez le VLAN propre du SSID sur la quarantaine, pas sur le personnel ; FreeRADIUS ici en envoie toujours un, mais le repli doit quand même échouer en mode fermé. Et le VLAN dynamique « is mutually exclusive with a captive portal, NAT mode, wireless client isolation, and a multicast DNS (mDNS) gateway »23, donc un portail invité sur le même SSID est exclu.

Le WPA3-Enterprise du WAX620, c’est le mode 192 bits : « the encryption is automatically set to GCMP256 »32, avec protection des trames de gestion exigée. Les clients qui ne savent pas faire de GCMP-256 ne s’y connecteront pas. Testez votre plus vieil appareil avant de basculer, comme pour TLS 1.3.

Les appareils, et la vérification que tout le monde saute

Chaque appareil a besoin de trois choses : son propre certificat et sa clé, le certificat racine, et le nom du serveur RADIUS auquel il doit s’attendre. La plupart des guides couvrent les deux premières. Laissez de côté quoi que ce soit de la troisième et ça marche quand même, et c’est bien le problème.

La troisième, c’est ce qui empêche un appareil de faire confiance au mauvais serveur. Ici, un faux serveur RADIUS a été monté deux fois : une fois avec un certificat parfaitement valide de la même AC, émis pour ha.home.example, et une fois avec un certificat auto-signé. Un portable a été pointé sur chacun avec trois réglages clients différents :

Le client vérifieVrai serveurMême AC, autre nomAuto-signé
L’AC et le nom du serveurSe connecteRefusé, Domain mismatchRefusé
L’AC seulementSe connecteSe connecteRefusé
RienSe connecteSe connecteSe connecte

Regardez la ligne du milieu. Votre AC signe un certificat pour chaque serveur web de la maison. Un appareil qui ne vérifie que l’AC acceptera n’importe lequel d’entre eux comme serveur RADIUS, donc quiconque met la main sur l’une de ces clés, depuis une machine de test oubliée ou un serveur web que personne n’a mis à jour, peut monter un point d’accès avec votre SSID et vos appareils s’y connecteront sans un murmure. Avec EAP-TLS, ils ne livrent aucun mot de passe, mais ils sont sur le réseau de l’attaquant et lui font confiance. TLS 1.3 n’y change rien ; les trois mêmes lignes sont revenues en 1.3. Seule la vérification du nom le fait.

Android y est arrivé le premier. L’option « Do not validate » a été retirée « for security reasons »33, tout en notant que « saved enterprise configurations that are set up to turn off server certificate validation aren’t affected »33. Un vieux téléphone peut encore en porter une.

Linux (NetworkManager)macOS, iOS, iPadOSAndroid
Certificat et clé802-1x.client-cert, 802-1x.private-keyPayload d’identité dans un profil de configurationInstallé comme certificat Wi-Fi
Certificat racine802-1x.ca-certPayload de certificat, approuvé via PayloadCertificateAnchorUUID34Champ du certificat d’AC
Nom du serveur802-1x.domain-match35TLSTrustedServerNames34Champ Domaine ; setDomainSuffixMatch dans l’API36
TLS 1.3802-1x.phase1-auth-flags tls-1-3-enableTLSMinimumVersion et TLSMaximumVersion "1.3" ; le maximum vaut "1.2" par défaut34setMinimumTlsVersion(TLS_V1_3), à partir du niveau d’API 34, là où l’appareil le prend en charge36

Sous Linux, le tout tient en une commande. Elle a été vérifiée avec nmcli --offline, qui affiche la connexion qu’il créerait sans toucher au système en cours :

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

Pour un port filaire, c’est la même chose avec type ethernet et sans SSID. La version TLS maximale d’Apple qui vaut 1.2 par défaut est le piège de ce côté-là : un profil qui l’omet produit un Mac que ce réseau refuse.

Ce que coûte le TLS 1.3 seul

La limite honnête de ce montage, ce sont les appareils qui ne savent pas le faire. Une caméra ou une télé avec un supplicant embarqué d’il y a des années peut gérer EAP-TLS en 1.2 et jamais en 1.3, et aucun réglage de votre côté ne corrige ça. La version de TLS est convenue avant que FreeRADIUS n’ait vu le certificat, donc il ne peut pas faire d’exception pour le rôle IoT.

Testez donc chaque appareil avant de l’acheter. Pas après. Pointez-le sur un FreeRADIUS en 1.3 seul et lisez le journal : recv TLS 1.3 Handshake, ClientHello ou protocol version vous dit tout ce qu’il faut savoir. Un appareil qui échoue va sur un port filaire avec MAB, ou ne va pas sur le réseau. Affaiblir tout le réseau pour l’adapter à la pire chose qui s’y trouve, c’est comme ça que le mot de passe partagé a commencé.

La configuration complète, fichier par fichier

Tout ce qui précède est réduit aux lignes qui font chaque démonstration. Voici le tout. Chaque fichier ici a tourné dans le labo, d’un step-ca vide et d’une arborescence FreeRADIUS d’origine jusqu’à un portable sur le VLAN 10, et les copies ci-dessous sont ces mêmes fichiers, avec les adresses du labo remplacées par celles du tableau. Prenez-les en un seul téléchargement, eap-tls-configs.tar.gz, ou fichier par fichier depuis les liens.

NomAdresseCe que c’est
ca.home.examplePar son nom seulementstep-ca, port 9000
radius.home.example192.0.2.10FreeRADIUS, RadSec sur 2083
radsecproxy192.0.2.11radsecproxy, UDP en entrée, RadSec en sortie
switch1, ap1192.0.2.21, 192.0.2.31Matériel qui parle RadSec
Matériel UDP seulement192.0.2.32 à .63Via radsecproxy ; le point d’accès Linux plus bas est ap2 sur .33
FichierVa dansSon rôle
step-ca/templates/device.tpl/home/step/templates/ dans l’ACLe rôle dans l’OU, authentification client seulement
step-ca/templates/nas.tpl/home/step/templates/ dans l’ACOU=nas, pour RadSec
step-ca/setup.shLancé une fois dans l’ACAjoute les provisionneurs network et nas
step-ca/issue.shLancé là où step fait confiance à l’ACTous les certificats, et la recette de révocation
freeradius/install.shLancé depuis freeradius/Met le reste en place
freeradius/mods-available/eapRADDB/mods-available/EAP-TLS, TLS 1.3, CRL vérifiée
freeradius/mods-available/device_vlanRADDB/mods-available/Lit la liste par appareil
freeradius/mods-available/mab_vlanRADDB/mods-available/Lit la liste MAB
freeradius/mods-config/device_vlan/devicesRADDB/mods-config/device_vlan/Nom de certificat vers VLAN
freeradius/mods-config/device_vlan/mabRADDB/mods-config/device_vlan/Adresse MAC vers VLAN
freeradius/policy.d/vlanRADDB/policy.d/Choisit le VLAN d’un certificat
freeradius/policy.d/mabRADDB/policy.d/Choisit le VLAN d’une MAC, 20 ou 99 seulement
freeradius/sites-available/default.patchAppliqué à RADDBAppelle les deux politiques
freeradius/sites-available/tlsRADDB/sites-available/L’écoute RadSec et ses clients
freeradius/nas-only.shRADDB/Ne laisse entrer que les certificats OU=nas
freeradius/clients.confRADDB/UDP pour les tests sur la machine elle-même
systemd/radius-crl-refresh/usr/local/sbin/Récupère et vérifie la CRL
systemd/radius-crl-refresh.service/etc/systemd/system/Le lance
systemd/radius-crl-refresh.timer/etc/systemd/system/Toutes les heures
systemd/radius-cert-renew.service/etc/systemd/system/Renouvelle le certificat serveur
radsecproxy/radsecproxy.conf/etc/radsecproxy.confUDP en entrée, RadSec en sortie
hostapd/hostapd-wired.conf/etc/hostapd/Un port de switch 802.1X sous Linux
hostapd/hostapd-wifi.conf/etc/hostapd/Un point d’accès Linux, un seul SSID
clients/wpa_supplicant-wired.confL’appareilConnexion filaire
clients/wpa_supplicant-wifi.confL’appareilConnexion WiFi
clients/nmcli.shL’appareilLa même chose, via NetworkManager

FreeRADIUS se trouve à un endroit différent sur chaque distribution, et ses fichiers s’en moquent, parce que chaque chemin qu’ils contiennent est construit à partir du répertoire de configuration. install.sh prend ce répertoire comme RADDB. Il a été lancé sur les trois ci-dessous, et la vérification de configuration de FreeRADIUS lui-même, -XC, est passée sur chacun. Seule l’image officielle a ensuite fait tourner tous les tests de cet article ; sur les deux autres, cette vérification est toute la preuve, dans un conteneur, donc SELinux sur une vraie machine Fedora n’en faisait pas partie.

Image officielleDebian 13Fedora
FreeRADIUS3.2.103.2.73.2.8
RADDB/etc/freeradius/etc/freeradius/3.0/etc/raddb
Tourne en tant quefreeradfreeradradiusd
ServiceLe conteneurfreeradiusradiusd
TestéTout ce qui est dans cet articleinstall.sh et -XCinstall.sh et -XC

Les deux unités systemd nomment /etc/freeradius et le service freeradius. Sur Debian ou Fedora, changez les deux selon le tableau. Rien d’autre ne bouge.

step-ca

Le modèle d’appareil est celui de Mettre le rôle dans le certificat, ligne pour ligne, avec un commentaire en tête. Le modèle nas suit la même idée, avec le rôle fixé :

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 ajoute les deux provisionneurs. Lancez-le une fois dans le conteneur step-ca. Il demande un mot de passe pour chacun, et le bloc CRL qu’il affiche va dans ca.json à la main :

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 fabrique tous les certificats de cet article, et garde la recette de révocation de la section sur la révocation en commentaire pour le jour où vous en aurez besoin. Cette recette a aussi été lancée contre cette AC, sur un laptop2 créé pour l’occasion :

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

Lancez install.sh depuis le répertoire freeradius du téléchargement. Il copie les fichiers, corrige le site par défaut et crée les liens. C’est tout ce qu’il fait :

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"

Le correctif tient en deux lignes dans le site par défaut d’origine. Il a été fait contre la 3.2.10 et s’applique à la 3.2.7 et à la 3.2.8 avec rien de pire qu’un décalage :

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 remplace celui d’origine. En entier :

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 est la politique vue plus haut, avec l’enveloppe if (&EAP-Message) que mentionne la section MAB :

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 est la politique de la section MAB, et mods-available/device_vlan et nas-only.sh sont tels que montrés plus haut, tous les trois avec un commentaire en plus. Le module de la liste MAB et les deux listes :

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 remplace l’exemple RadSec d’origine. Le répertoire temporaire de verify est ${run_dir}, qui existe déjà et appartient à l’utilisateur sous lequel tourne FreeRADIUS, donc il n’y a rien à créer :

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 remplace celui d’origine et ne laisse entrer que eapol_test sur la machine elle-même. Les switchs et les points d’accès arrivent par RadSec ou par le 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
}

Vérifiez d’abord la configuration. Puis lancez-le au premier plan, avec les threads activés pour que RadSec fonctionne :

freeradius -XC
freeradius -fxx -l stdout

Le tenir à jour

radius-crl-refresh récupère la CRL avec la racine épinglée, et refuse une liste qui n’est pas signée par l’intermédiaire ou qui est déjà périmée, donc une récupération ratée laisse l’ancienne liste en place. Il n’échange jamais une bonne liste contre du rebut :

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

Le certificat serveur vit 24 heures, donc il se renouvelle tout seul. Le renouvellement a gardé ici la clé et les permissions des deux fichiers, ce qui compte parce que FreeRADIUS les lit en tant que freerad. Il redémarre FreeRADIUS au lieu d’envoyer un HUP. Même raison que pour la 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

Une machine Linux qui tient le rôle d’un port de switch, en parlant RadSec. C’est le fichier qu’a utilisé le port simulé :

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

Et pour un point d’accès. Celui-ci passe par radsecproxy en UDP, comme le ferait un point d’accès Netgear, et c’est le fichier qu’ont utilisé les radios simulées :

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

Les appareils

nmcli.sh est la commande de la section sur les appareils et son double filaire. Sans NetworkManager, wpa_supplicant prend ceux-ci :

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

Contre quoi chaque fichier a été testé

FichiersTesté contreRésultat
step-ca/step-ca 0.30.2, une nouvelle ACLes 7 certificats émis avec leurs rôles ; laptop2 émis puis révoqué par la recette
freeradius/3.2.10, installé par install.shlaptop1 VLAN 10, cam1 99, tv1 30, un certificat nas refusé ; MAB aabbccddeeff VLAN 20, une MAC inconnue refusée
radius-crl-refreshstep-ca en TLS 1.3CRL installée et hachée ; le portable révoqué refusé en moins de cinq minutes, sans redémarrage
radius-cert-renew.servicestep ca renew 0.30.2Certificat renouvelé ; clé et permissions intactes
radsecproxy.confradsecproxy 1.11.2EAP-TLS accepté à travers lui ; MAB avec Message-Authenticator VLAN 20, sans lui écarté
hostapd-wired.confhostapd 2.11, compilé avec RadSecVLAN 10, 99 et 30 en TLS 1.3
hostapd-wifi.conf, wpa_supplicant-wifi.confRadios simulées, via radsecproxyTrois stations sur un SSID, VLAN 10, 99 et 30 ; le fichier client tel qu’écrit et une station WPA3 seul ont tous deux rejoint
wpa_supplicant-wired.confeapol_test 2.11Les mêmes réponses que le tableau plus haut
Unités systemdsystemd-analyze verifyAucune plainte, sauf que les deux programmes ne sont pas installés ici

Ce qui bloque tout le monde

Chacun de ces cas a été rencontré ou testé pendant ce montage, à part le VLAN manquant sur un trunk, qu’il faut un vrai switch pour montrer. Presque tous échouent en mode fermé. C’est la bonne façon d’échouer, et ça reste une mauvaise matinée.

PanneCe que vous voyezCorrectif
CRL expirée ou absenteTous les appareils refusés ; CRL has expired ou unable to get certificate CRLRécupérer la CRL sur minuterie et la surveiller
Nouvelle CRL non prise en compteUn appareil révoqué entre encoreca_path_reload_interval = 300, ou redémarrer FreeRADIUS
Le client a TLS 1.3 désactivéprotocol version, appareil refusétls_disable_tlsv1_3=0, tls-1-3-enable ou les clés du profil Apple
Fichiers de certificat illisibles pour freeradFreeRADIUS ne démarre pas, Permission deniedLes rendre lisibles par freerad, la clé par lui seul
Certificat sans rôleRefusé par la politiqueRéémettre via le provisioner network avec --set role=
VLAN absent du switch ou du trunk du point d’accèsL’appareil s’authentifie, n’obtient pas d’adresseCréer le VLAN et l’étiqueter sur chaque trunk que l’appareil peut atteindre
Mauvais secret partagé RADIUSPas de réponse ; FreeRADIUS journalise invalid Message-Authenticator! (Shared secret is incorrect.) et le switch réessaie jusqu’à abandonnerLe même secret dans clients.conf et sur le switch ou le point d’accès
Appareil qui ne vérifie pas le nom du serveurRien. Ça marche, et ça marcherait aussi avec un imposteurRégler le nom du serveur sur chaque appareil
Requête sans Message-AuthenticatorJetée en silence ; le switch réessaie et abandonneL’activer sur le switch, ou placer radsecproxy devant
Écouteur RadSec sous freeradius -XThreading must be enabled for TLS socketsfreeradius -fxx -l stdout
hostapd compilé sans RadSecunsupported RADIUS type 'TLS'Compiler avec CONFIG_RADIUS_TLS=y, ou utiliser radsecproxy
radsecproxy configuré avec l’adresse du serveurNo subjectaltname matching addressUtiliser radius.home.example

Pourquoi n’est-ce pas sur chaque réseau d’entreprise par défaut ?

Rien dans cet article n’est nouveau. EAP-TLS a été publié en octobre 199937. Les attributs de VLAN ont suivi en septembre 20033. Les deux fabricants de switchs ci-dessus documentent le VLAN dynamique dans leurs propres manuels, et l’AC, le serveur RADIUS et le proxy sont open source et ne coûtent rien. Passez par la voie commerciale et la même chose devient une ligne de facture : Cisco met « RADIUS authentication, authorization, and accounting, including 802.1X, MAC authentication bypass » dans le niveau Essentials d’ISE, licencié par « concurrent active endpoints », où un endpoint peut être l’un des « printers, fax machines, or other types of network devices »38 du réseau. Dans un cas comme dans l’autre, la capacité dort dans le matériel depuis plus de vingt ans.

Alors pourquoi un réseau d’entreprise en 2026 tourne-t-il encore sur un mot de passe que tout le monde partage ?

Puis-je demander pourquoi tant d’administrateurs et de MSP ne s’en donnent tout simplement pas la peine ? Pas qui. Ce qui permet que ça continue. Il y a trois réponses honnêtes, parce qu’il y a trois cas.

Certains ne savent pas. Et c’est ça la faute, pas l’excuse. Savoir ce que fait votre propre switch n’est pas quelque chose qui vous arrive. S’assurer de le savoir, c’est le métier, et le manuel qui le dit se télécharge gratuitement.

Certains savent, et laissent faire. Le mot de passe partagé marche. Personne n’ouvre de ticket à son sujet. Un certificat par appareil, c’est une série de corvées qui ne montrent rien jusqu’au jour où un portable disparaît, et en tant que tel ça ne gagne jamais de place sur la liste. Pour un MSP, le calcul est plus simple encore : un réseau plat avec un seul mot de passe coûte moins cher à maintenir, aucun contrat ne le mesure, et le client ne demande jamais. La série sur les MSP suit là où mènent ces incitations, et c’est le même schéma sur une facture plus petite. C’est ce cas-là que vise la question : l’administrateur qui a le switch qui sait le faire, qui a lu jusqu’ici, et qui garde la clé partagée parce que personne ne l’obligera à s’en justifier.

Certains ne peuvent vraiment pas, pas encore. L’imprimante sans supplicant, la télé qui ne fera pas de TLS 1.3, la machine métier que son éditeur ne mettra pas à jour. C’est une vraie contrainte et la réponse est oui, c’est défendable : MAB sur un VLAN clôturé, la raison écrite, et une date à côté pour le départ de la machine.

La faute n’est pas d’avoir un trou. Tout le monde a des trous. C’est de faire tourner un réseau d’entreprise au-dessus d’un trou qu’on n’est jamais allé boucher, sur du matériel livré capable de le boucher.

Tout le monde a fait tourner un mot de passe partagé un jour. En garder un sur un réseau d’entreprise par choix, c’est la partie qui n’est pas défendable.

Pourquoi Cyber Essentials ne le demande pas

Parce que ça n’a jamais été son but. Cyber Essentials est le socle soutenu par le gouvernement britannique, et « a growing number of organisations require suppliers to be certified to bid for work »39. Il est construit contre « the most common internet based cyber security threats »39, et ses exigences sont explicites sur l’endroit où il s’arrête. Le matériel sans fil est hors périmètre là où un attaquant ne peut pas l’atteindre depuis internet, parce que « the Cyber Essentials scheme isn’t concerned with attacks that can only be launched within the signal range of the wireless device »40. Ses cinq contrôles sont les pare-feu, la configuration sécurisée, les mises à jour de sécurité, le contrôle des accès utilisateurs et la protection contre les logiciels malveillants40. Aucun ne demande qui a le droit d’entrer sur le réseau au départ. Le plus proche qu’il s’en approche, c’est « change any default or guessable account passwords »40, et une clé WiFi imprimée sur le comptoir de l’accueil n’est pas un compte.

Les VLAN y apparaissent bien, une fois. Un « sub-set » d’une organisation, un « segregated from the rest of the organisation by a firewall or VLAN »40, peut être évalué seul. Le seul endroit où le dispositif croise un VLAN, c’est donc comme moyen de tracer la limite autour d’une plus petite partie du réseau.

Selon ses propres termes, c’est juste. C’est un plancher contre les attaques venant d’internet, et il le dit. Le problème, c’est ce qui arrive à un plancher une fois qu’il a un certificat accroché au mur. Il devient la cible, puis le plafond, et une entreprise peut l’obtenir avec chaque appareil sur un seul réseau plat derrière un mot de passe que connaît la femme de ménage. Quiconque passe devant le bâtiment avec un portable est dans le modèle de menace qui compte et hors de celui qui a été évalué.

Un mot de passe est une promesse que personne ne tient

Ce qui m’a frappé en construisant ça, c’est le peu de choses difficiles. Chaque piège était écrit quelque part, dans un commentaire de configuration ou un manuel de fabricant, et chacun a cédé en un après-midi de labo.

Et pourtant la maison tourne sur un mot de passe WiFi écrit sur le frigo. Pas parce que l’alternative est difficile, mais parce que le mot de passe est le défaut, et que le défaut n’a jamais à se justifier. Tout le monde sait qu’il est partagé. Tout le monde sait qu’il fuit. Personne ne le change, parce que le changer veut dire faire le tour de chaque appareil de l’endroit avec le nouveau, alors il reste le même pendant des années, à travers chaque invité, chaque locataire et chaque téléphone parti au recyclage avec lui encore enregistré, et il laisse entrer tous ceux qui l’ont jamais eu.

Un certificat par appareil, c’est une autre idée de ce à quoi sert le réseau. Le réseau doit savoir ce qu’est une chose, pas seulement que quelqu’un y a un jour tapé le bon mot. C’est une norme qu’on s’impose sans que personne ne vérifie, et c’est la seule qui vaille. La caméra n’a pas à se retrouver à côté du portable parce qu’elle se trouvait sur le même SSID. Elle se trouve là où vous l’avez mise, sur du matériel qui vous appartient, sous une règle que vous avez écrite et que vous pouvez lire. Quand elle se comporte mal, elle se déplace avec une ligne dans un fichier texte. Quand elle disparaît, une commande la met dehors.

Le prix, ce sont des corvées. Une CRL avec une minuterie derrière, des certificats avec une date dans l’agenda, des appareils testés avant d’être achetés. C’est le coût, et il est dans le bon sens : vous payez en effort que vous voyez, au lieu de payer en mot de passe qui laisse discrètement entrer des gens depuis des années.


  1. RFC 3748 — Extensible Authentication Protocol (EAP) — EAP et les rôles de pair, d’authentificateur et de serveur d’authentification entre lesquels il circule. ↩︎

  2. RFC 2868 — RADIUS Attributes for Tunnel Protocol Support — définit les attributs de tunnel que la RFC 3580 réutilise pour les VLAN. ↩︎

  3. RFC 3580 — IEEE 802.1X RADIUS Usage Guidelines — section 3.31, « Tunnel-Type=VLAN (13) Tunnel-Medium-Type=802 Tunnel-Private-Group-ID=VLANID », et « 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 — authentification mutuelle par certificat sur 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 » ; la section 2.1.7 recommande des NAI anonymes dans la réponse d’identité. ↩︎ ↩︎

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

  8. Smallstep — Certificate templates — les données utilisateur passées avec --set sont disponibles dans le modèle sous .Insecure.User ; toujours envelopper les valeurs dans toJson. ↩︎

  9. FreeRADIUS 3.2.10 — dictionary.freeradius.internal — les attributs TLS-Client-Cert-* que lit la politique, dont TLS-Client-Cert-Subject et 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) », et domain_match, la « Constraint for server domain name ». ↩︎

  12. RFC 2548 — Microsoft Vendor-specific RADIUS Attributes — MS-MPPE-Send-Key et MS-MPPE-Recv-Key, chiffrés comme « b(1) = MD5(S + R + A) c(1) = p(1) xor b(1) », où S est le secret partagé. ↩︎

  13. BlastRADIUS — CVE-2024-3596 ; l’atténuation recommandée est « 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 et limit_proxy_state ; mettre les deux à « no » « will allow MITM attackers to create fake Access-Accept packets to the NAS! » ↩︎

  16. RFC 6614 — TLS Encryption for RADIUS — RadSec, mai 2012, expérimentale. ↩︎

  17. IETF — RadSec : RADIUS over TLS and DTLS — statut visé Proposed Standard, rendant obsolètes les RFC 6614 et RFC 7360 ; état IESG « Has enough positions to pass » lors de la vérification du 13 octobre 2026. ↩︎

  18. FreeRADIUS 3.2.10 — sites-available/tls — l’écouteur RadSec, require_client_cert, proto = tls, et « 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 », exigeant les derniers firmwares de points d’accès et de switchs. ↩︎ ↩︎

  21. TP-Link Omada — 802.1X configuration — RADIUS Proxy : « It does not support the RadSec feature » ; avec 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 » ; aucune mention de 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 », et « 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 ». Testé ici avec la 1.11.2. ↩︎

  25. RFC 9765 — RADIUS/1.1 — avril 2025, expérimentale : « 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 est « 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. » Le manuel du M4350 porte le même texte. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  28. TP-Link Omada — Dynamic VLAN with the VLAN Assignment function of RADIUS — switchs : Authentication Type « Port Based », « enable ‘VLAN Assignment’ », « make sure the 802.1X Control mode is Auto » ; points d’accès : « Enable VLAN Assignment for Wireless Network » dans le profil RADIUS. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  29. TP-Link — Configuration guide for 802.1X VLAN assignment and MAB — « VLAN Assignment takes effect only when the control type is Port Based » ; les VLAN manquants sont créés automatiquement ; MAB envoie la MAC sous forme de « 12 hexadecimal digits, all lowercase and no punctuation ». ↩︎ ↩︎ ↩︎ ↩︎

  30. Netgear — GS710TUP user manual — le contrôle de port MAC-based « allows multiple supplicants connected to the same port ». ↩︎

  31. hostapd — hostapd.conf — VLAN dynamique « 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 et vlan_bridge. Lu depuis le paquet 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 vaut 1.2 par défaut. ↩︎ ↩︎ ↩︎

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

  36. Android Developers — WifiEnterpriseConfig — setDomainSuffixMatch ; TLS_V1_3 « Added in API level 34 », avec WifiManager.isTlsV13Supported() indiquant « whether or not TLS v1.3 is supported ». ↩︎ ↩︎

  37. RFC 2716 — PPP EAP TLS Authentication Protocol — la spécification EAP-TLS d’origine, octobre 1999, remplacée depuis par la RFC 5216. ↩︎

  38. Cisco — ISE licences — Essentials couvre « 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 » ; il protège contre « the most common internet based cyber security threats ». ↩︎ ↩︎

  40. NCSC — Cyber Essentials : Requirements for IT Infrastructure v3.3 — avril 2026. Les appareils sans fil sont hors périmètre là où ils ne peuvent pas être attaqués depuis internet : « the Cyber Essentials scheme isn’t concerned with attacks that can only be launched within the signal range of the wireless device » ; les cinq contrôles techniques ; « change any default or guessable account passwords » ; un sub-set « is part of the organisation whose network is segregated from the rest of the organisation by a firewall or VLAN ». ↩︎ ↩︎ ↩︎ ↩︎