Les passerelles de réseaux privés virtuels (VPN) déployées sur l'Internet public traitent en permanence un flux continu de paquets non authentifiés. En conditions nominales, les tunnels cryptographiques vérifient l'identité des pairs à l'aide d'échanges de clés asymétriques. Cependant, lors d'attaques par déni de service distribué (DDoS), des acteurs malveillants exploitent une asymétrie de coût informatique : l'émission de millions de paquets d'initialisation UDP forgés ne consomme qu'une bande passante minime, tandis que la passerelle réceptrice doit exécuter des opérations cryptographiques intensives et allouer des structures de session en mémoire noyau.

Les architectures traditionnelles comme OpenVPN ou IPsec IKEv2 peinent à absorber ces charges. Elles créent des blocs de contrôle de transmission ou des mémoires tampons d'association de sécurité (SA) dès la première sollicitation, saturant ainsi les files d'attente du système d'exploitation. WireGuard neutralise ce vecteur d'attaque. En appliquant un principe strict d'absence d'état (stateless) avant l'établissement complet du tunnel, et en exploitant une validation de code d'authentification de message à deux niveaux (mac1 et mac2) adossée à des cookies tournants, WireGuard préserve les ressources de ses serveurs.

Au sein du noyau Linux, ce rempart défensif est implémenté dans le module drivers/net/wireguard/cookie.c. Analyser sa conception cryptographique démontre comment des tunnels modernes maintiennent une résilience totale sans concéder de mémoire aux émetteurs illégitimes.

L'asymétrie de la poignée de main et la résilience sans état

Le motif Noise IK employé par WireGuard repose sur une authentification mutuelle via des multiplications scalaires Diffie-Hellman sur la courbe Curve25519 (X25519). Effectuer une multiplication de point sur une courbe elliptique exige un volume important de cycles processeur en comparaison d'un simple routage de paquet. Si un serveur VPN acceptait chaque paquet Type 1: Handshake Initiation pour calculer immédiatement le secret partagé, un assaillant usurpant des adresses IP sources pourrait monopoliser tous les cœurs de processeur, privant les utilisateurs légitimes de bande passante.

WireGuard surmonte cet écueil en segmentant la validation des paquets en paliers indépendants :

[ Paquet d'initialisation reçu ]
              │
              ▼
   Vérification MAC1 (Clé statique)
      ├─ Non concordant ──► Rejet silencieux (Zero Response)
      └─ Valide
              │
              ▼
   Passerelle sous forte charge ? (ratelimiter.c)
      ├─ Non ──► Traitement scalaire Curve25519
      └─ Oui
              │
              ▼
   Vérification MAC2 (Cookie tournant)
      ├─ Valide         ──► Traitement scalaire Curve25519
      └─ Non concordant ──► Émission Type 3: Cookie Reply (Sans état)

En régime courant, lorsque le volume reste sous les seuils définis par ratelimiter.c, le serveur valide mac1 et poursuit la négociation de clés. Dès que le trafic menace de saturer le processeur, la passerelle active sa seconde ligne de défense : elle exige la présence d'un jeton mac2 valide, calculé à partir d'un cookie émis par le serveur.

Anatomie du contrôle MAC à deux niveaux (mac1 et mac2)

Chaque paquet d'initialisation WireGuard réserve deux champs de 16 octets à la fin de sa structure : mac1 (octets 116 à 131) et mac2 (octets 132 à 147). Ces champs forment des filtres complémentaires :

  1. Filtre mac1 : Calculé par la formule BLAKE2s-128(key = Hash(Label-Mac1 || Responder_PublicKey), message = packet[0..115]). Puisque mac1 utilise une clé dérivée de la clé publique du serveur, seuls les clients configurés avec l'identité exacte du serveur peuvent générer un tag valide. Les paquets provenant de sondes aléatoires échouent et sont abandonnés sans générer de réponse ICMP.
  2. Filtre mac2 : Calculé par la formule BLAKE2s-128(key = Cookie, message = packet[0..131]). Le champ mac2 couvre l'intégralité du paquet, y compris le bloc mac1 déjà validé. La clé de 16 octets correspond à un cookie cryptographique éphémère transmis exclusivement aux adresses IP ayant prouvé leur authenticité.

Dans des conditions normales, le client remplit le champ mac2 avec seize octets nuls (0x00). Lorsque le serveur bascule en mode de protection contre la saturation, tout paquet dont le champ mac2 est nul ou incorrect est écarté des calculs cryptographiques. Au lieu d'enregistrer une session, le serveur déclenche une séquence défi-réponse sans état.

Lorsque ratelimiter.c signale une surcharge, la fonction wg_cookie_checker_create_payload() du module cookie.c prend le relais. Le noyau assemble un paquet de 64 octets nommé Type 3: Handshake Cookie Reply :

┌────────────────────────────────────────────────────────┐
│ Type : 3 (1 octet) │ Réservé : 0 (3 octets)            │
├────────────────────────────────────────────────────────┤
│ Index récepteur : sender_index du client (4 octets)    │
├────────────────────────────────────────────────────────┤
│ Nonce XChaCha20 : Valeur aléatoire CSPRNG (24 octets)  │
├────────────────────────────────────────────────────────┤
│ Jeton Cookie chiffré : XChaCha20-Poly1305 (32 octets)  │
│ - 16 octets de cookie en clair                         │
│ - 16 octets de tag d'authentification Poly1305         │
└────────────────────────────────────────────────────────┘

L'assemblage de ce paquet de réponse réclame une charge processeur insignifiante. Point déterminant : le serveur n'alloue aucun octet en mémoire dynamique (kmalloc = 0). Le paquet renvoie au client son propre sender_index afin d'identifier la tentative en attente, tandis que le contenu du cookie est protégé par le chiffrement authentifié XChaCha20-Poly1305.

La clé de chiffrement symétrique du cookie est dérivée de façon déterministe :

Cookie_Encryption_Key = BLAKE2s-256(
    key = Hash(Label-Cookie || Responder_PublicKey),
    message = Initiator_MAC1
)

Comme la valeur Initiator_MAC1 a déjà été validée par le serveur, elle agit comme un sel cryptographique propre à cette tentative, empêchant les tiers d'anticiper la clé de déchiffrement.

Rotation de l'entropie secrète et preuve de propriété d'adresse IP

La propriété essentielle du mécanisme de WireGuard réside dans sa capacité à vérifier que l'émetteur possède son adresse IP sans conserver d'historique en mémoire. Le serveur calcule le cookie à partir d'un secret local de 32 octets (cookie_checker->secret), généré par le générateur de nombres pseudo-aléatoires du noyau (get_random_bytes).

Ce calcul lie l'adresse réseau de l'émetteur au jeton :

Cookie = BLAKE2s-128(
    key = cookie_checker->secret,
    message = Initiator_IP_Address || Initiator_Port
)

Pour empêcher l'accumulation de cookies par des assaillants sur de longues durées, le serveur renouvelle ce secret toutes les 120 secondes (COOKIE_SECRET_MAX_AGE = 120).

Pour tolérer les variations de latence réseau et les paquets en transit, cookie.c conserve deux générations de clés :

  • secret[0] : Le secret actif utilisé pour forger les nouveaux cookies et vérifier les arrivées récentes.
  • secret[1] : Le secret précédent issu de l'intervalle antérieur, permettant d'authentifier les paquets lors des transitions de clés.

Lors d'une attaque par usurpation d'adresse source, le serveur renvoie les paquets Type 3: Cookie Reply vers ces adresses IP falsifiées en UDP. Ne contrôlant pas ces points de routage distants, l'attaquant ne peut jamais récupérer le cookie chiffré.

À l'inverse, le client légitime reçoit la réponse et exécute wg_cookie_message_consume(). Il déchiffre le jeton, le place dans sa mémoire locale (peer->latest_cookie), horodate l'entrée avec cookie_birthdate = jiffies, puis réémet immédiatement sa demande d'initialisation avec le champ mac2 renseigné. Le serveur vérifie mac2 en temps constant O(1), confirme la légitimité de l'adresse et engage sereinement les calculs Curve25519.

Concurrence sans verrou et architecture RiceGuard

L'efficacité du module cookie.c repose sur son architecture sans verrou bloquant (lockless). La lecture des secrets tournants et le calcul des condensats MAC s'exécutent au sein d'une section critique rcu_read_lock(). L'absence de spinlocks exclusifs sur le chemin de réception permet à tous les cœurs processeurs de valider mac1 et mac2 en parallèle sans goulot d'étranglement mémoire.

Paquet entrant ──► [File Rx NIC] ──► Lecture RCU ──► Vérification BLAKE2s ──► Fil d'exécution

Dans les environnements industriels à très fort trafic, les passerelles VPN doivent faire face à des rafales de dizaines de millions de paquets par seconde. Chez VPNRice, le moteur RiceGuard renforce ce dispositif noyau en intégrant cookie.c aux pilotes eBPF et XDP (eXpress Data Path).

En validant les deux niveaux de MAC directement dans le pilote réseau avant l'allocation des tampons sk_buff, RiceGuard neutralise les attaques d'usurpation dès la frontière matérielle. Les utilisateurs bénéficient ainsi de connexions d'une fluidité remarquable, garantissant une confidentialité persistante et une latence minime même sous forte contrainte réseau.