From c9c4cc0af10f917aad69c7fe92222e285fb131fd Mon Sep 17 00:00:00 2001 From: Toni Date: Wed, 22 Jul 2026 13:53:30 +0200 Subject: [PATCH] wacky encryption --- {shared => client}/crypto.go | 2 +- client/main.go | 143 ++++++++++++++++++++++++++++++----- go.mod | 5 +- go.sum | 2 + server/main.go | 93 +++++++++++++---------- shared/proto.go | 26 +++++-- 6 files changed, 204 insertions(+), 67 deletions(-) rename {shared => client}/crypto.go (98%) diff --git a/shared/crypto.go b/client/crypto.go similarity index 98% rename from shared/crypto.go rename to client/crypto.go index 83193f5..a03b1dc 100644 --- a/shared/crypto.go +++ b/client/crypto.go @@ -1,4 +1,4 @@ -package shared +package main import ( "crypto/rand" diff --git a/client/main.go b/client/main.go index e83e1af..e6bf140 100644 --- a/client/main.go +++ b/client/main.go @@ -1,23 +1,24 @@ package main import ( - "encoding/base64" + "crypto/sha256" "encoding/binary" "encoding/hex" "log" "net" "os" "os/exec" + "time" "baalvpn/shared" + "filippo.io/mlkem768/xwing" "github.com/songgao/water" ) const ( SERVER_IP = "172.20.12.47" IFACE_NAME = "baalvpn" - PSK = "rj5FvWB0n342C6UGrAS0gI3GX5ya+lDmC7/XmgFHF+s=" ) var ( @@ -25,7 +26,14 @@ var ( serverAddr *net.UDPAddr iface *water.Interface conn *net.UDPConn - pskKey []byte + privKey *xwing.DecapsulationKey + + // TODO: this should probably have a mutex + peerPubKeys = map[string][]byte{} + // TODO: this should probably have a mutex + peerSessionKeys = map[string][]byte{} + // TODO: this should probably have a mutex + peerEstablishAcks = map[string]bool{} ) func main() { @@ -33,12 +41,6 @@ func main() { panic("root permissions needed") } - var err error - pskKey, err = base64.StdEncoding.DecodeString(PSK) - if err != nil { - panic(err) - } - listenAddr, err := net.ResolveUDPAddr("udp", ":38000") if err != nil { panic(err) @@ -61,12 +63,63 @@ func main() { } func register() { - req := shared.BuildPkt(shared.REQ_REGISTER, []byte{}) + var err error + privKey, err = xwing.GenerateKey() + if err != nil { + panic(err) + } + pubKey := privKey.EncapsulationKey() + + req := shared.BuildPkt(shared.REQ_REGISTER, pubKey) if _, err := conn.WriteToUDP(req, serverAddr); err != nil { panic(err) } } +func getOrEstablishSessionKey(ip string) []byte { + if key, ok := peerSessionKeys[ip]; ok { + return key + } else { + log.Println("requesting pubkey of " + ip) + req := shared.BuildPkt(shared.REQ_GET_PUBKEY, net.ParseIP(ip)) + if _, err := conn.WriteToUDP(req, serverAddr); err != nil { + panic(err) + } + + for { + // TODO: eww + time.Sleep(50 * time.Millisecond) + if peerPubKey, ok := peerPubKeys[ip]; ok { + log.Println("got pubkey of " + ip) + + ciphertext, sharedSecret, err := xwing.Encapsulate(peerPubKey) + if err != nil { + panic(err) + } + + reqData := append(net.ParseIP(ip), append(internalIP, ciphertext...)...) + req := shared.BuildPkt(shared.REQ_ESTABLISH, reqData) + if _, err := conn.WriteToUDP(req, serverAddr); err != nil { + panic(err) + } + + for { + // TODO: eww + time.Sleep(50 * time.Millisecond) + if peerEstablishAcks[ip] { + delete(peerEstablishAcks, ip) + break + } + } + + sessionKey := sha256.Sum256(sharedSecret) + peerSessionKeys[ip] = sessionKey[:] + return sessionKey[:] + } + } + } +} + func receivePackets() { for { buffer := make([]byte, 50000) @@ -106,8 +159,12 @@ func receivePacket(pkt []byte) { log.Println("got assigned IP by the server:", internalIP.String()) setupInterface() go sendPackets() - case shared.RESP_DATA: - decryptedPkt, err := shared.DecryptSym(pskKey, pkt[4:]) + case shared.ENC_PKT: + srcIP := net.IP(pkt[20:36]).String() + + sessionKey := getOrEstablishSessionKey(srcIP) + + decryptedPkt, err := DecryptSym(sessionKey, pkt[36:]) if err != nil { panic(err) } @@ -116,6 +173,37 @@ func receivePacket(pkt []byte) { if _, err := iface.Write(decryptedPkt); err != nil { log.Println(err) } + case shared.BROADCAST_PKT: + log.Println("received pkt:", hex.EncodeToString(pkt[4:])) + if _, err := iface.Write(pkt[4:]); err != nil { + log.Println(err) + } + case shared.RESP_GET_PUBKEY: + respIP := net.IP(pkt[4:20]).String() + pubKey := pkt[20:] + peerPubKeys[respIP] = pubKey + case shared.REQ_ESTABLISH: + srcIP := net.IP(pkt[20:36]).String() + ciphertext := pkt[36:] + + sharedSecret, err := xwing.Decapsulate(privKey, ciphertext) + if err != nil { + panic(err) + } + + sessionKey := sha256.Sum256(sharedSecret) + peerSessionKeys[srcIP] = sessionKey[:] + + log.Println("received session key from", srcIP) + reqData := append(net.ParseIP(srcIP), internalIP...) + req := shared.BuildPkt(shared.RESP_ESTABLISH, reqData) + if _, err := conn.WriteToUDP(req, serverAddr); err != nil { + panic(err) + } + case shared.RESP_ESTABLISH: + srcIP := net.IP(pkt[20:36]).String() + log.Println("established session key with", srcIP) + peerEstablishAcks[srcIP] = true default: log.Println("unknown packet type") } @@ -150,14 +238,33 @@ func sendPackets() { } pkt = pkt[:n] - encryptedPkt, err := shared.EncryptSym(pskKey, pkt) - if err != nil { - panic(err) + etherType := binary.BigEndian.Uint16(pkt[12:14]) + payload := pkt[14:] + + if etherType != 0x86dd { // IPv6 + continue } - req := shared.BuildPkt(shared.REQ_DATA, encryptedPkt) - if _, err := conn.WriteToUDP(req, serverAddr); err != nil { - log.Println(err) + destIP := net.IP(payload[24:40]).String() + + if payload[24] == 0xff { // multicast + req := shared.BuildPkt(shared.BROADCAST_PKT, pkt) + if _, err := conn.WriteToUDP(req, serverAddr); err != nil { + log.Println(err) + } + } else { + destSessionKey := getOrEstablishSessionKey(destIP) + + encryptedPkt, err := EncryptSym(destSessionKey, pkt) + if err != nil { + panic(err) + } + + reqBody := append(net.ParseIP(destIP), append(internalIP, encryptedPkt...)...) + req := shared.BuildPkt(shared.ENC_PKT, reqBody) + if _, err := conn.WriteToUDP(req, serverAddr); err != nil { + log.Println(err) + } } } } diff --git a/go.mod b/go.mod index 671c592..a32655a 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,9 @@ module baalvpn go 1.26.3 require ( + filippo.io/mlkem768 v0.0.0-20260214141301-2e7bebc7d88d github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 golang.org/x/crypto v0.54.0 ) -require ( - golang.org/x/sys v0.47.0 // indirect -) +require golang.org/x/sys v0.47.0 // indirect diff --git a/go.sum b/go.sum index a4f2d2e..ce2f8ac 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +filippo.io/mlkem768 v0.0.0-20260214141301-2e7bebc7d88d h1:YyLyABjdrdt2l/E6JAnku4BjhEDXhxQD2bPOnOvy8/M= +filippo.io/mlkem768 v0.0.0-20260214141301-2e7bebc7d88d/go.mod h1:ym4egWKLpazdho3bHx0xuQlCq02ttP+vhxxKO8LgO9c= github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8= github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= diff --git a/server/main.go b/server/main.go index d1b7902..aa7e54a 100644 --- a/server/main.go +++ b/server/main.go @@ -2,11 +2,13 @@ // TODO: authentication // TODO: somehow persist IPs // TODO: handle multiple peers behind one NAT +// TODO: key rotation +// TODO: sha256 -> HKDF +// TODO: recovery package main import ( "crypto/rand" - "encoding/base64" "encoding/binary" "log" "net" @@ -19,16 +21,13 @@ import ( type Peer struct { RealAddr *net.UDPAddr InternalIP string + PublicKey []byte } -// TODO: replace with key exchange between peers -const PSK = "rj5FvWB0n342C6UGrAS0gI3GX5ya+lDmC7/XmgFHF+s=" - var ( peers = map[string]*Peer{} peersMutex sync.Mutex conn *net.UDPConn - pskKey []byte ) func main() { @@ -36,12 +35,6 @@ func main() { panic("root permissions needed") } - var err error - pskKey, err = base64.StdEncoding.DecodeString(PSK) - if err != nil { - panic(err) - } - receivePackets() } @@ -91,6 +84,8 @@ func handleReq(req []byte, addr *net.UDPAddr) { peersMutex.Lock() defer peersMutex.Unlock() + pubKey := req[4:] + internalIP := randomIP().String() if destPeer, ok := peers[addr.IP.String()]; ok { @@ -99,12 +94,13 @@ func handleReq(req []byte, addr *net.UDPAddr) { peers[addr.IP.String()] = &Peer{ RealAddr: addr, InternalIP: internalIP, + PublicKey: pubKey, } } resp := shared.BuildPkt(shared.RESP_REGISTER, net.ParseIP(internalIP)) sendTo(addr, resp) - case shared.REQ_DATA: + case shared.ENC_PKT: peersMutex.Lock() peer := peers[addr.IP.String()] peersMutex.Unlock() @@ -113,38 +109,55 @@ func handleReq(req []byte, addr *net.UDPAddr) { return } - pkt, err := shared.DecryptSym(pskKey, req[4:]) - if err != nil { - log.Printf("failed to decrypt packet from %s: %s", peer.RealAddr, err) + destIP := net.IP(req[4:20]).String() + srcIP := net.IP(req[20:36]).String() + if srcIP != peer.InternalIP { + log.Println("rejected spoofed srcIP in ENC_PKT") return } - etherType := binary.BigEndian.Uint16(pkt[12:14]) - payload := pkt[14:] - - switch etherType { - case 0x86dd: // IPv6 - rawDestIP := payload[24:40] - destIP := net.IP(rawDestIP).String() - - encryptedPkt, err := shared.EncryptSym(pskKey, pkt) - if err != nil { - panic(err) - } - resp := shared.BuildPkt(shared.RESP_DATA, encryptedPkt) - - if rawDestIP[0] == 0xff { // multicast - log.Println(peer.InternalIP + " -> " + destIP + " (*)") - broadcast(resp, peer.InternalIP) - } else if destPeer := getPeerByInternalIP(destIP); destPeer != nil { - log.Println(peer.InternalIP + " -> " + destIP) - sendTo(destPeer.RealAddr, resp) - } else { - log.Println(peer.InternalIP + " -/> " + destIP + " (unrecognized IP)") - } - default: - log.Printf("unhandled EtherType: 0x%x\n", etherType) + if destPeer := getPeerByInternalIP(destIP); destPeer != nil { + log.Println(srcIP + " -> " + destIP) + sendTo(destPeer.RealAddr, req) + } else { + log.Println(srcIP + " -/> " + destIP + " (unrecognized IP)") } + case shared.BROADCAST_PKT: + peersMutex.Lock() + peer := peers[addr.IP.String()] + peersMutex.Unlock() + if peer == nil { + log.Println("data from unregistered peer:", addr.String()) + return + } + + destIP := net.IP(req[42:58]).String() + log.Println(peer.InternalIP + " -> " + destIP) + broadcast(req, peer.InternalIP) + case shared.REQ_GET_PUBKEY: + peerIP := net.IP(req[4:20]).String() + peer := getPeerByInternalIP(peerIP) + if peer == nil { + log.Println(addr.String(), "tried to get pubkey of unknown peer", peerIP) + return + } + + resp := shared.BuildPkt(shared.RESP_GET_PUBKEY, append(net.ParseIP(peer.InternalIP), peer.PublicKey...)) + sendTo(addr, resp) + case shared.REQ_ESTABLISH, shared.RESP_ESTABLISH: + peersMutex.Lock() + peer := peers[addr.IP.String()] + peersMutex.Unlock() + + srcIP := net.IP(req[20:36]).String() + if srcIP != peer.InternalIP { + log.Println("rejected spoofed srcIP in ESTABLISH") + return + } + + dstIP := net.IP(req[4:20]).String() + dstPeer := getPeerByInternalIP(dstIP) + sendTo(dstPeer.RealAddr, req) default: log.Println("unknown packet type") } diff --git a/shared/proto.go b/shared/proto.go index fc11a50..fc00e65 100644 --- a/shared/proto.go +++ b/shared/proto.go @@ -6,14 +6,30 @@ const PROTO_VERSION uint16 = 1 const ( unused uint16 = iota - // - empty - + // (client -> server) requests an IP + // [ pubkey - 1216 bytes ] REQ_REGISTER + // (server -> client) assigns an IP // [ ip - 16 bytes ] RESP_REGISTER - // [ encrypted pkt ] - REQ_DATA - // [ encrypted pkt ] - RESP_DATA + // (client -> server -> client2) relays an encrypted packet to a specified peer + // [ destIP - 16 bytes ] [ srcIP - 16 bytes ] [ encrypted pkt ] + ENC_PKT + // (client -> server -> *) broadcasts an unsecured packet + // [ plaintext pkt ] + BROADCAST_PKT + // (client -> server) requests peer's pubkey from the server for encapsulation + // [ ip - 16 bytes ] + REQ_GET_PUBKEY + // (server -> client) provides requested pubkey + // [ ip - 16 bytes ] [ pubkey - 1216 bytes ] + RESP_GET_PUBKEY + // (client -> server -> client2) establishes a session key with another peer + // [ destIP - 16 bytes ] [ srcIP - 16 bytes ] [ ciphertext - 1120 bytes ] + REQ_ESTABLISH + // (client2 -> server -> client) acknowledges the session key was established + // [ destIP - 16 bytes ] [ srcIP - 16 bytes ] + RESP_ESTABLISH ) func BuildPkt(pktType uint16, data []byte) []byte {