WireGuard

From
Revision as of 09:37, 22 October 2018 by SaschaT (talk | contribs)
Jump to navigation Jump to search

WireGuard ist eine relativ neue, vollständig im Kernel Space implementierte VPN-Lösung. Sie wurde als Ersatz insbsondere für IPsec, aber auch für User-Space-Lösungen wie OpenVPN entwickelt.

Motivation

Bisherige VPN-Lösungen sind i.d.R. sehr komplex. Laut Angaben des Entwicklers kommt die IPsec-Implementierung StrongSwan auf ca. 420.0000 LOC und OpenVPN auf ca. 120.000 LOC, während WireGuard mit weniger als 4.000 auskommt. Zudem gibt es für bisherige Lösungen i.d.R. viele Konfigurationsmöglichkeiten, eine sichere IPsec-Konfiguration zu erstellen, wird oft als schwieriger und langwieriger Prozess angesehen. WireGuard hingegen verzichtet auf jede Konfigurierbarkeit der kryptographischen Komponenten, die Konfigurationsmöglichkeiten beschränken sich auf das nötigste. Durch die Kompaktheit des Codes, die durch diese Einschränkungen möglich wird, versprechen sich die Entwickler eine vergleichsweise einfache Überprüfbarkeit.

Umsetzung

Grundlegendes

Um die Versprechen: Schnelligkeit, Sicherheit und Einfachheit zu realisieren wurden beim Design von WireGuard moderne kryptografische Prinzipien gewählt und auf eine übersichtliche Implementierung als Kernelmodul für Linux geachtet. Alle Daten werden grundsätzlich verschlüsselt und unter Gewährleistung von Perfect Forward Secrecy (PFS) übertragen. Auch eine Verschleierung der Identitäten der Netzwerkteilnehmer soll vom Protokoll gegenüber Dritten erreicht werden. Optional können die Peers (über einen beliebigen sicheren Kanal) symmetrische Schlüssel austauschen, um einen leichten Post Quantum Cryptography Schutz zu erreichen. Die Implementierung von WireGuard bietet ein Interface der Schicht 3 des ISO/OSI Schichtenmodells für Netzwerkprotokolle. Ein Service zum initialen Austausch öffentlicher Schlüssel wird daher von WireGuard nicht angeboten. Innerhalb der Schicht wird die Einhaltung einer Schichtenarchitektur jedoch zugunsten einer einfacheren Implementierbarkeit aufgegeben. Nutzer können WireGuard über eine transparente virtuelle Netzwerkschnittstelle verwenden, die mit Standardtools der Netzwerkadministration wie iptables angepasst werden kann. Die Identitäten aller Netzwerkteilnehmer sind dabei eindeutig an ihre öffentlichen Schlüssel geknüpft, welche vor der Konfiguration zwischen den Teilnehmern ausgetauscht werden müssen. Neben der Verwendung moderner kryptografischer Prinzipien stützt sich das Sicherheitskonzept von WireGuard auf ein methodisches Design, bei dem der Versuch unternommen wird, die Komplexität von WireGuard so gering wie möglich zu halten, typische Schwachstellen wie dynamische Speicherallokation zu vermeiden und Vorkehrungen gegen gängige Angriffe zu treffen. Zu diesen Vorkehrungen zählen Zeitstempel in Handshake Nachrichten (Replay-Angriffe) und ein Crypro-Cookie Mechanismus (CPU-Exhaustion Angriff), ein optionaler Teil des Handshakes.

Verwendete kryptografische Funktionen

WireGuard verzichtet auf so genannte "cipher agility" und beschränkt sich daher auf drei kryptografische Grundfunktionen. Symmetrische Verschlüsselung erfolgt mittels ChaCha20Poly1305 im Authenticated Encryption with Associated Data (AEAD) Modus. Verschlüsselte Daten werden also grundsätzlich auf Integrität geprüft und authentifiziert. Der Schlüsselaustausch im Handshake erfolgt in Form eines Elliptic Curve Diffie-Hellman (ECDH) Schlüsselaustauschs mittels Curve25519. Als universelle Hashfunktion wird BLAKE2s eingesetzt. Diese wird für die Generierung von Message Authentication Codes (MAC) , Keyed-Hash Message Authentication Codes (HMAC) und die Schlüsselableitung über HMAC-based Key Derivation Funciton (HKDF) eingesetzt. Die Verwendung der Linux Kernel Crypto API wurde zugunsten einer eigenen Implementierung der kryptografischen Primitiven aufgegeben.

Protokoll

Das von WireGuard verwendete Protokoll wurde auf Basis des Noise Protocol Framework entworfen und zeichnet sich durch die Verwendung statischer Header fester Länge aus, was eine abwärtskompatible Erweiterung erschwert. Es nutzt einen 1-RTT Handshake und implementiert einen Krypto-Cookie-Mechanismus. Das Design basiert auf einem Zustandsautomaten, über den das Protokoll formal verifiziert wurde.

Nachrichten des WireGuard - Protokolls
Message Type Beschreibung Antwort auf
Initiate Handshake Message Initiiert den Austausch eines Sitzungsschlüssels. Kann von beiden Endpunkten angestoßen werden. Keine oder Cookie Reply Message
Handshake Response Message Schließt den Austausch eines Sitzungsschlüssels ab. Initiate Handshake Message, falls diese vom Empfänger akzeptiert wurde.
Cookie Reply Message Dient zur Vorbeugung von DOS - Angriffen (CPU-Exhaustion Attack). Wird vom Empfänger einer Initiate Handshake Message versendet, falls dieser unter Überlast leidet, MAC1 erfolgreich geprüft wurde und MAC2 nicht gesetzt ist. Initiate Handshake Message, bei Überlast.
Transport Messages Dient zum Transport von Nutzdaten zwischen beiden Endpunkten. Kann nur versendet werden, wenn ein gültiger Sitzungsschlüssel vorliegt. Keine

Insgesamt versendet WireGuard vier verschiedene Nachrichtentypen, die zwischen zwei Peers ausgetauscht werden können. Dabei entfallen drei der Nachrichtentypen in den Bereich des Handshakes. Der Verbindungsaufbau und Schlüsselaustausch kann von beiden Endpunkten mittels der Initiate Handshake Message angestoßen werden. Nach Abschluss des Handshakes können beide Endpunkte Daten übertragen, solange die Sitzung gültig ist. Eine Sitzung ist höchstens 180 Sekunden oder für Versand von rund 2^64 Nachrichten gültig, danach muss der Handshake wiederholt werden. Dies dient dem Erhalt von PFS und vermeidet einen unsicheren Betrieb der verwendeten Stromchiffre zur symmetrischen Verschlüsselung der Nachrichten. Der Cookie-Mechanismus soll CPU-exhaustion Angriffe vermeiden und ist so entworfen, dass er nicht seinerseits für einen DOS-Angriff genutzt werden kann. Ein Man-In-The-Middle (MITM) mit Kenntnis der öffentlichen Schlüssel der kommunizierenden Endpunkte kann diesen Schutz zwar überwinden, wäre allerdings ohnehin in der Lage einen Denial of Service (DOS) Angriff durchzuführen.

Handshake

Der Handshake dient dazu, einen Sitzungsschlüssel zwischen beiden Endpunkten auszutauschen. Um dies erfolgreich tun zu können, müssen beide Endpunkte die öffentlichen Schlüssel des jeweils anderen kennen und über den passenden eigenen, privaten Schlüssel verfügen. Zum besseren Verständnis des Ablaufs des Schlüsselaustauschs sollen hier einige wesentliche Felder der Initiate Handshake Message näher erläutert werden.

Felder der Initiate Handshake Message des WireGuard - Protokolls
Feld Beschreibung
type & reversend Identifikation des Nachrichtentypes.
sender Zufälliger Wert, der es ermöglicht eine Nachricht schnell einer Sitzung zuzuordnen.
ephemeral Ein temporärer öffentlicher ECDH-Schlüssel, der zur Generierung des Sitzungsschlüssels verwendet wird.
static Der per AEAD symmetrisch verschlüsselte und damit unkenntliche öffentliche Schlüssel des Senders dieser Nachricht. Der Empfänger kann über diesen Wert die Authentizität der Nachricht prüfen (zweite/dritte Authentifizierungsstufe), ohne dass eine abgefangene Nachricht zur Identifikation des Senders genutzt werden kann.
timestamp Ein per AEAD symmetrisch verschlüsselter Zeitstempel. Der Empfänger merkt sich den jeweils letzten empfangenen Zeitstempel und akzeptiert nur die Handshakenachrichten eines Endpunkts, die einen größeren Zeitstempel enthalten als die zuletzt empfangene. Auf diese Weise werden replay Angriffe vermieden.
mac1 MAC über die versendete Nachricht. Der für die Berechnung der MAC verwendete Schlüssel wird auf Basis des öffentlichen Schlüssels des Empfängers generiert. Der Empfänger prüft bei Erhalt zuerst diesen Wert und verwirft die Nachricht, falls die Prüfung misslingt (erste Authentifizierungsstufe). Ist dem Sender der Nachricht der öffentliche Schlüssel des Empfängers nicht bekannt, kann er diesen Prüfwert nicht berechnen.
mac2 MAC über die versendete Nachricht. Als Schlüssel dient der zuletzt empfangene gültige Cookie. Der Cookie ist ein zufälliger Wert, der vom Empfänger alle 120 Sekunden neu generiert wird. Er wird in einem eigenen Nachrichtentyp verschlüsselt übertragen, falls der Empfänger unter Überlast leidet. In diesem Fall schließt er einen Handshake nur dann ab, wenn mac2 einen gültigen Wert enthält (zweite Authentifizierungsstufe). Die Verschlüsselung des Cookies erfolgt per AEAD, wobei als Schlüssel der öffentliche Schlüssel des Senders dient. Mit Hilfe des Cookies können CPU-Exhaustion Angriffe von gewöhnlichen Angreifern vermieden werden, er ist jedoch gegen einen MITM mit Kenntnis der öffentlichen Schlüssel der Endpunkte zwecklos.

Um einen Handshake durchzuführen, erzeugt der Sender ein flüchtiges ECDH-Schlüsselpaar und setzt bzw. berechnet die oben beschriebenen Felder. MAC2 kann nur berechnet werden, falls zuvor ein Cookie empfangen wurde und dieser noch gültig ist. Der Empfänger führt eine zwei-/dreistufige Authentifizierung der Nachricht durch, bevor eine Antwort versendet wird. Die Antwort ist ähnlich aufgebaut wie die Initiate Handshake Message und dient im Wesentlichen der Übermittlung eines flüchtigen öffentlichen ECDH-Schlüssels des Empfängers. Auf Basis der beiden flüchtigen ECDH-Schlüsselpaare können beide Endpunkte denselben symmetrischen Sitzungsschlüssel generieren.

Schematisch folgt die Kommunikation zweier Endpunkten einem von zwei möglichen Mustern:

  1. Sender sendet Initiate Handshake Message an Empfänger.
  2. Empfänger sendet Handshake Response Message an Sender.
  3. Transport Messages können beliebig ausgetauscht werden.


  1. Sender sendet Initiate Handshake Message an Empfänger(Überlast).
  2. Empfänger sendet Cookie Reply Message an Sender.
  3. Sender sendet Initiate Handshake Message mit gesetztem MAC2 Feld an Empfänger.
  4. Empfänger sendet Handshake Response Message an Sender.
  5. Transport Messages können beliebig ausgetauscht werden.

Installation und Konfiguration

WireGuard ist als Paket für die meisten Linux-Distributionen verfügbar, alternativ kann es natürlich auch selbst kompiliert werden. Die Wireguard-Pakete bestehen i.d.R. aus einem Paket mit Userspace Utilities zur einfachen Konfiguration (wg und wg-quick) sowie aus dem Kernelmodul.

Soll WireGuard als VPN-Server verwendet werden, der Clients die Mitnutzung seiner Internetverbindung ermöglicht, muss nach der Installation zunächst IP-Forwarding im Kernel aktiviert werden. Hierzu wird folgende Zeile in der Datei /etc/sysctl.d/99-sysctl.conf auskommentiert oder ergänzt:

net.ipv4.ip_forward = 1

Die WireGuard-Konfigurationsdatei auf dem Server (z.B. /etc/wireguard/wg0.conf für ein Interface wg0) folgt dem folgenden Schema:

[Interface]
PrivateKey = <hier private key des Servers einfügen>
ListenPort = 5555
Address = 10.0.0.1/24
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o enp0s3 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o enp0s3 -j MASQUERADE

[Peer]
PublicKey = <hier public key des ersten Clients einfügen>
AllowedIPs = 10.0.0.2/32

Dabei ist Address im Abschnitt Interface die VPN-IP-Adresse des Servers, die iptables-Befehle in PostUp und PostDown werden automatisch ausgeführt, um das korrekte Forwarding der Pakete an die Clients zu ermöglichen. AllowedIPs im Abschnitt Peers ist die VPN-IP-Adresse des Peers. Für weitere Clients können weitere Peer-Abschnitte hinzugefügt werden.

Die Konfiguration eines Clients könnte wie folgt aussehen:

[Interface]
PrivateKey = <hier Private Key des Clients einfügen>
ListenPort = 5555
Address = 10.0.0.2/24

[Peer]
PublicKey = <hier Public Key des Servers einfügen>
AllowedIPs = 0.0.0.0/0, ::0/0
Endpoint = 192.168.56.101:5555
PersistentKeepalive = 25

Hier agiert der Server als Peer. Der Endpoint ist die öffentliche IP und der Port des Servers und für den Verbindungsaufbau notwendig. AllowedIPs definiert, auf welche IP-Adressen der Client über den Server zugreifen möchte, hier können z.B. lokale und Netzwerk-IPs ausgeschlossen werden. 0.0.0.0/0 bedeutet, dass alle IPs über den Server zugegriffen werden sollen. Mit PersistentKeepalive kann eine kontinuierliche Kommunikation auch ohne Netzwerkverkehr erzwungen werden, dies ist v.a. für Clients hinter einem NAT notwendig. Als weitere Option kann im Interface-Bereich ein zu verwendender DNS-Server festgelegt werden.

Alternativ kann WireGuard auch über die interfaces-Datei oder systemd konfiguriert werden.

WireGuard kann dann über den folgenden Befehl gestartet werden, dabei wird das Interface automatisch erzeugt:

systemctl start wg-quick@wg0

Unterstützte Betriebssysteme

Derzeit werden GNU/Linux, macOS, FreeBSD, OpenBSD, OpenWRT und Android unterstützt. An einer Userspace-Implementierung, die dann auch auf Windows funktionieren soll, wird nach Aussage der Entwickler derzeit gearbeitet.

Es ist bereits eine proprietäre Closed Source Windows-Implementierung mit dem Namen "TunSafe" von einem anderen Entwickler verfügbar, die auf dem TUN/TAP-Treiber von OpenVPN basiert. Der WireGuard-Hauptentwickler Donenfeld rät von der Verwendung dieser Implementation explizit ab.

Usability

Die Clients benötigen für die Verbindung mit einem WireGuard-Server eine Konfigurationsdatei, die unter anderem ihren Private Key und den Public Key des Servers enthält. Aus Sicherheitsgründen ist es natürlich empfehlenswert, den Private Key auf dem Client zu generieren, allerdings muss der Nutzer dann die Konfigurationsdatei selbst erstellen. Dies erfordert zumindest Grundkenntnisse über die darin vorhandenen Parameter.

Letztendlich werden viele Serverbetreiber wohl die Konfigurationsdateien selbst generieren und den Clients zur Verfügung stellen, damit dieser Schritt entfällt. Für den Andoid-Client kann zudem ein QR-Code mit der Konfiguration erstellt werden.

Mit dem folgenden Skipt wgmanage.sh, das auf dem Server ausgeführt werden kann, lassen sich automatisiert Client-Konfigurationen und QR-Codes erzeugen und direkt zur Server-Konfiguration hinzfügen, sodass die Clients sich sofort verbinden können.

#!/bin/bash
SERVER_PUBLIC_KEY="PASTE PUBLIC KEY HERE"
SERVER_PUBLIC_IP="192.168.56.101"
SERVER_PORT="5555"
CLIENT_SUBNET="24"
# end of config

CLIENT_PRIVATE=`wg genkey`
CLIENT_PUBLIC=`echo "$CLIENT_PRIVATE" | wg pubkey`
if [ $# -lt 2 ] || (([ $# -lt 3 ] && [ $1 == "-a" ]) || ([ $1 != "-a" ] && [ $1 != "-r" ])); then
        echo "Usage (add client): wgmanage -a CLIENT_IP CLIENT_OUTFILE [QR_OUTFILE]"
        echo "Usage (remove client): wgmanage -r CLIENT_IP"
        exit
fi
if [ $1 == "-a" ]; then
        echo -e "[Interface]\nAddress = $2/$CLIENT_SUBNET\nPrivateKey = $CLIENT_PRIVATE\n\n[Peer]\nPublicKey = $SERVER_PUBLIC_KEY\nAllowedIPs = 0.0.0.0/0, ::0/0\nEndpoint = $SERVER_PUBLIC_IP:$SERVER_PORT" > $3
        echo -e "\n[Peer]\nPublicKey = $CLIENT_PUBLIC\nAllowedIPs = $2/32" >> /etc/wireguard/wg0.conf
        systemctl restart wg-quick@wg0
        if [ $# -ge 4 ]; then
                qrencode -o $4 < $3
        fi
        exit
fi
if [ $1 == "-r" ]; then
        echo "Not implemented yet"
fi

Das Skript kann aufgerufen werden mit:

Client hinzufügen und QR-Code im Terminal anzeigen: ./wgmanage.sh -a [Client-IP] [Konfiguratiosdatei] Client hinzufügen und QR-Code als png speichern: ./wgmanage.sh -a [Client-IP] [Konfigurationsdatei] [QR-Datei]

Das Skript kann bei Bedarf natürlich auch so erweitert werden, dass Clients auch aus der WireGuard-Serverkonfiguration entfernt werden können. Diese Funktion ist bereits im Skript vorgesehen, aber noch nicht implementiert.


Quellen