Linux 5.6, rilasciato oggi, include WireGuard nel kernel mainline: il driver wireguard non è più un modulo out-of-tree da compilare con DKMS, ma codice che arriva insieme al kernel. È il momento in cui un protocollo VPN nato fuori dall’albero ufficiale diventa parte dell’interfaccia di rete standard di Linux.
Contesto
Le due VPN più diffuse nel 2020 si portano dietro parecchi anni e parecchio codice. IPsec è un insieme di RFC (ESP, IKEv1, IKEv2, modalità transport e tunnel) che stack come strongSwan e libreswan implementano. OpenVPN, del 2001, gira in userspace sopra OpenSSL e trasporta TLS dentro un tunnel UDP o TCP. Funzionano entrambe e sono ampiamente revisionate, ma la loro flessibilità — negoziazione delle cipher suite, modalità multiple, decine di opzioni di configurazione — è anche la loro superficie d’attacco.
WireGuard, scritto da Jason A. Donenfeld, parte dal vincolo opposto: niente negoziazione. Il primo paper accademico, WireGuard: Next Generation Kernel Network Tunnel, è del 2017 e descrive un tunnel che vive nel kernel come una normale interfaccia di rete (wg0), dove l’instradamento lo gestisce la tabella di routing del sistema e non un demone separato. Il cuore nel kernel sta in poche migliaia di righe, contro le centinaia di migliaia degli stack tradizionali: meno codice da leggere prima di potersi pronunciare sulla sua sicurezza.
Architettura crittografica
WireGuard fissa le primitive dentro il protocollo. Non c’è una fase in cui le parti concordano un algoritmo:
- Curve25519 per lo scambio di chiavi Diffie-Hellman su curva ellittica
- ChaCha20-Poly1305 come cifrario AEAD (Authenticated Encryption with Associated Data) per i dati di trasporto
- BLAKE2s per l’hashing e per la derivazione di chiavi (HKDF, Hash-based Key Derivation Function)
L’handshake poggia sul Noise Protocol Framework di Trevor Perrin, nello specifico sul pattern IKpsk2: l’iniziatore conosce già la chiave pubblica statica del responder (K), invia subito la propria statica cifrata (I), e un’eventuale pre-shared key entra nell’ultimo messaggio (psk2). In pratica sono due messaggi — handshake initiation e handshake response — seguiti dai transport data. L’iniziatore può trasmettere dati appena ricevuta la response; il responder aspetta un primo transport data dall’iniziatore prima di considerare l’handshake confermato.
L’assenza di negoziazione ha una conseguenza diretta: non esiste attacco di downgrade verso una cifra più debole, perché non c’è una cifra alternativa da imporre. Quando una primitiva andrà cambiata, cambia la versione del protocollo, non si rinegozia a runtime. È una scelta che toglie opzioni, e con loro le combinazioni da analizzare.
Punto critico
La parte che secondo me merita più attenzione di quanta ne riceva è la gestione del carico. Un responder che esegue un’operazione asimmetrica costosa per ogni pacchetto in arrivo è esposto a un flood: l’attaccante spende poco, il difensore molto. WireGuard se ne occupa con i MAC e i cookie descritti nel paper.
Ogni messaggio porta due campi di autenticazione, mac1 e mac2. mac1 è calcolato sulla chiave pubblica statica del destinatario e scarta subito i pacchetti di chi non la conosce, prima ancora di toccare la crittografia asimmetrica. Sotto carico, il responder può rispondere con un cookie reply invece di elaborare l’handshake: il cookie è legato all’indirizzo IP sorgente del mittente, che deve poi includerlo in mac2 al tentativo successivo. Così chi inizia è costretto a dimostrare di poter davvero ricevere traffico all’indirizzo che dichiara, e lo spoofing diventa più caro. Il cookie è a sua volta cifrato, per non trasformarsi in un oracolo che riveli se a un dato IP gira WireGuard.
Sul lato client c’è un dettaglio facile da sbagliare quando si reimplementa il protocollo: ricevuto un cookie reply, il peer non deve rilanciare subito, ma memorizzare il cookie decifrato e aspettare lo scadere del Rekey-Timeout prima del tentativo successivo, per non amplificare a sua volta il traffico.
Implicazioni operative
Una configurazione tipica sta in una decina di righe: una sezione [Interface] con chiave privata e indirizzo, una [Peer] per controparte con chiave pubblica, AllowedIPs ed eventuale Endpoint. AllowedIPs serve a due cose insieme: è la tabella di instradamento per quel peer ed è il filtro crittografico in ingresso. Un pacchetto cifrato da una chiave viene accettato solo se la sorgente rientra negli AllowedIPs associati a quella chiave. È il cryptokey routing descritto nel paper, e sposta il controllo d’accesso dal livello applicativo a un legame diretto tra chiave e prefisso.
Il modello è simmetrico: nel protocollo non esiste un ruolo client e uno server, solo peer con chiavi. Gli endpoint possono cambiare indirizzo IP — un peer mobile che passa dal Wi-Fi alla rete cellulare continua la sessione senza rinegoziare — perché l’identità è la chiave pubblica, non l’indirizzo. L’interfaccia è anche silenziosa: senza traffico legittimo non risponde, e una scansione cieca fatica a individuarla.
Prima della 5.6 chi voleva WireGuard usava il modulo out-of-tree con DKMS, oppure le implementazioni userspace: wireguard-go in Go come riferimento, e boringtun in Rust pubblicato da Cloudflare. La strada verso mainline ha richiesto più di un giro di revisioni; la prima proposta portava con sé una libreria crittografica a sé stante, zinc, e il merge è arrivato dopo aver riscritto il codice sopra l’API crittografica già presente nel kernel.
Limiti
Il controllo d’accesso resta legato alla coppia chiave-prefisso. Nel protocollo non c’è autenticazione di utente o di ruolo: distribuire le chiavi, revocarle, mappare una persona su un peer sono compiti da costruire sopra, con strumenti esterni. Chi ha vincoli di compliance che impongono una cifra specifica — validazione FIPS, chiavi in HSM — deve verificare che il set fisso di WireGuard sia ammesso, perché dall’interno non si cambia. Il NAT traversal è più scarno di quello che offrono OpenVPN su UDP o IPsec con NAT-T, e negli scenari di NAT difficili serve un peer con endpoint stabile a fare da punto d’incontro. Il logging è minimo per scelta progettuale: l’osservabilità va aggiunta. L’ingresso in mainline, infine, è l’inizio di una fase, non la fine: ora il codice è esposto a una platea di revisione molto più ampia, ed è da quell’esposizione prolungata che si misurerà la solidità delle scelte, non dal solo fatto dell’inclusione.
https://www.wireguard.com/papers/wireguard.pdf https://www.wireguard.com/protocol/ https://noiseprotocol.org/noise.html https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tag/?h=v5.6 https://www.noze.it/insights/wireguard-kernel/
Immagine di copertina: Logo di WireGuard: un drago serpentino stilizzato in rosso e nero accanto alla scritta “WireGuard” in caratteri grassetti — logo di WireGuard LLC, pubblico dominio — https://commons.wikimedia.org/wiki/File:Logo_of_WireGuard.png