diff --git a/README.md b/README.md index bc05f14..f09ea27 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # baalvpn -A post-quantum VPN for IPv6 +A hub-and-spoke VPN for IPv6 ## Features * **Post-quantum security:** Uses [XWingMLKEM768X25519](https://datatracker.ietf.org/doc/html/draft-connolly-cfrg-xwing-kem/) + [XChaCha20-Poly1305](https://en.wikipedia.org/wiki/ChaCha20-Poly1305#XChaCha20-Poly1305_%E2%80%93_extended_nonce_variant) + [HKDF-SHA-256](https://en.wikipedia.org/wiki/HKDF) for end-to-end encryption diff --git a/client/main.go b/client/main.go index 948bb55..68d0e9d 100644 --- a/client/main.go +++ b/client/main.go @@ -16,6 +16,7 @@ import ( "github.com/songgao/water" ) +// TODO: parse some sort of config+key file const ( SERVER_IP = "172.20.12.47" IFACE_NAME = "baalvpn" @@ -69,13 +70,14 @@ func register() { } pubKey := privKey.PublicKey() + // TODO: this probably should be repeated until we get RESP_REGISTER send(shared.BuildPkt(shared.REQ_REGISTER, pubKey.Bytes())) } func receivePackets() { + buffer := make([]byte, 50000) for { - buffer := make([]byte, 50000) - n, addr, err := conn.ReadFromUDP(buffer) + n, addr, err := conn.ReadFromUDP(buffer[:]) if err != nil { log.Println(err) continue @@ -88,7 +90,9 @@ func receivePackets() { continue } - go handleIncomingPkt(buffer[:n]) + req := make([]byte, n) + copy(req, buffer[:n]) + go handleIncomingPkt(req) } } @@ -195,6 +199,7 @@ func getOrEstablishSessionKey(ip string) []byte { } // TODO: this definitely shouldnt block the main thread + // TODO: this should try like 10 times tops since server doesnt respond at all if it doesnt have the pubkey for { // request pubkey every 50ms time.Sleep(50 * time.Millisecond) @@ -233,7 +238,7 @@ func getOrEstablishSessionKey(ip string) []byte { } func setupInterface() { - config := water.Config{DeviceType: water.TAP} + config := water.Config{DeviceType: water.TUN} config.Name = IFACE_NAME var err error @@ -261,16 +266,14 @@ func relayPackets() { } pkt = pkt[:n] - etherType := binary.BigEndian.Uint16(pkt[12:14]) - payload := pkt[14:] - - if etherType != 0x86dd { // IPv6 + version := pkt[0] >> 4 + if version != 6 { continue } - destIP := net.IP(payload[24:40]).String() + destIP := net.IP(pkt[24:40]).String() - if payload[24] == 0xff { // multicast + if pkt[24] == 0xff { // multicast encryptedPkt, err := EncryptSym(multicastKey, pkt) if err != nil { panic(err) diff --git a/server/main.go b/server/main.go index 569daa9..70a9055 100644 --- a/server/main.go +++ b/server/main.go @@ -1,14 +1,15 @@ // TODO: slice bounds checking // TODO: authentication // TODO: somehow persist IPs -// TODO: handle multiple peers behind one NAT // TODO: key rotation +// TODO: dont trust claimed IP at all package main import ( "crypto/hpke" "crypto/rand" "encoding/binary" + "encoding/hex" "log" "net" "os" @@ -17,14 +18,15 @@ import ( ) type Peer struct { - RealAddr *net.UDPAddr - InternalIP string - PublicKey hpke.PublicKey + RealAddr *net.UDPAddr + InternalIP string + HexPublicKey string } var ( peers = shared.NewTMap[string, *Peer]() peersByInternal = shared.NewTMap[string, *Peer]() + peersByPubKey = shared.NewTMap[string, *Peer]() conn *net.UDPConn multicastKey = make([]byte, 32) ) @@ -54,8 +56,8 @@ func receivePackets() { } defer conn.Close() + buffer := make([]byte, 50000) for { - buffer := make([]byte, 50000) n, addr, err := conn.ReadFromUDP(buffer) if err != nil { log.Println(err) @@ -65,14 +67,16 @@ func receivePackets() { continue } - go handleReq(buffer[:n], addr) + req := make([]byte, n) + copy(req, buffer[:n]) + go handleReq(req, addr) } } func handleReq(req []byte, addr *net.UDPAddr) { defer func() { if r := recover(); r != nil { - log.Printf("recovered from panic while handling request from %s: %v\n", addr.IP.String(), r) + log.Printf("recovered from panic while handling request from %s: %v\n", addr.String(), r) } }() @@ -96,19 +100,25 @@ func handleReq(req []byte, addr *net.UDPAddr) { return } - internalIP := randomIP().String() + hexPubKey := hex.EncodeToString(pubKey.Bytes()) - if destPeer, ok := peers.GetOK(addr.IP.String()); ok { - internalIP = destPeer.InternalIP - destPeer.PublicKey = pubKey + var internalIP string + // TODO: authenticating only by PUBLIC key is obviously a bad idea + if peer, ok := peersByPubKey.GetOK(hexPubKey); ok { + internalIP = peer.InternalIP + peers.Delete(peer.RealAddr.String()) + peer.RealAddr = addr + peers.Set(addr.String(), peer) } else { + internalIP = randomIP().String() peer := &Peer{ - RealAddr: addr, - InternalIP: internalIP, - PublicKey: pubKey, + RealAddr: addr, + InternalIP: internalIP, + HexPublicKey: hexPubKey, } - peers.Set(addr.IP.String(), peer) + peers.Set(addr.String(), peer) peersByInternal.Set(internalIP, peer) + peersByPubKey.Set(hexPubKey, peer) } ciphertext, err := hpke.Seal(pubKey, hpke.HKDFSHA256(), hpke.ChaCha20Poly1305(), []byte("baalvpn"), multicastKey) @@ -119,7 +129,7 @@ func handleReq(req []byte, addr *net.UDPAddr) { resp := shared.BuildPkt(shared.RESP_REGISTER, append(net.ParseIP(internalIP), ciphertext...)) sendTo(addr, resp) case shared.ENC_PKT: - peer := peers.Get(addr.IP.String()) + peer := peers.Get(addr.String()) if peer == nil { log.Println("data from unregistered peer:", addr.String()) return @@ -139,7 +149,7 @@ func handleReq(req []byte, addr *net.UDPAddr) { log.Println(srcIP + " -/> " + destIP + " (unrecognized IP)") } case shared.BROADCAST_PKT: - peer := peers.Get(addr.IP.String()) + peer := peers.Get(addr.String()) if peer == nil { log.Println("data from unregistered peer:", addr.String()) return @@ -150,15 +160,16 @@ func handleReq(req []byte, addr *net.UDPAddr) { case shared.REQ_GET_PUBKEY: peerIP := net.IP(req[4:20]).String() peer := peersByInternal.Get(peerIP) - if peer == nil { - log.Println("tried to get pubkey of unknown peer") - return + if peer != nil { + pubKey, err := hex.DecodeString(peer.HexPublicKey) + if err != nil { + panic(err) + } + resp := shared.BuildPkt(shared.RESP_GET_PUBKEY, append(net.ParseIP(peerIP), pubKey...)) + sendTo(addr, resp) } - - resp := shared.BuildPkt(shared.RESP_GET_PUBKEY, append(net.ParseIP(peer.InternalIP), peer.PublicKey.Bytes()...)) - sendTo(addr, resp) case shared.REQ_ESTABLISH, shared.RESP_ESTABLISH: - peer := peers.Get(addr.IP.String()) + peer := peers.Get(addr.String()) if peer == nil { log.Println("unregistered peer tried to ESTABLISH") return