diff --git a/client/main.go b/client/main.go index e34d271..e83e1af 100644 --- a/client/main.go +++ b/client/main.go @@ -1,6 +1,7 @@ package main import ( + "encoding/base64" "encoding/binary" "encoding/hex" "log" @@ -10,13 +11,13 @@ import ( "baalvpn/shared" - "github.com/songgao/packets/ethernet" "github.com/songgao/water" ) const ( SERVER_IP = "172.20.12.47" IFACE_NAME = "baalvpn" + PSK = "rj5FvWB0n342C6UGrAS0gI3GX5ya+lDmC7/XmgFHF+s=" ) var ( @@ -24,6 +25,7 @@ var ( serverAddr *net.UDPAddr iface *water.Interface conn *net.UDPConn + pskKey []byte ) func main() { @@ -31,6 +33,12 @@ 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) @@ -99,8 +107,13 @@ func receivePacket(pkt []byte) { setupInterface() go sendPackets() case shared.RESP_DATA: - log.Println("received pkt:", hex.EncodeToString(pkt[4:])) - if _, err := iface.Write(pkt[4:]); err != nil { + decryptedPkt, err := shared.DecryptSym(pskKey, pkt[4:]) + if err != nil { + panic(err) + } + + log.Println("received pkt:", hex.EncodeToString(decryptedPkt)) + if _, err := iface.Write(decryptedPkt); err != nil { log.Println(err) } default: @@ -129,17 +142,21 @@ func setupInterface() { func sendPackets() { log.Println("Listening for packets...") - var frame ethernet.Frame for { - frame.Resize(1500) - n, err := iface.Read([]byte(frame)) + pkt := make([]byte, 1500) + n, err := iface.Read([]byte(pkt)) if err != nil { panic(err) } - frame = frame[:n] + pkt = pkt[:n] - pkt := shared.BuildPkt(shared.REQ_DATA, frame) - if _, err := conn.WriteToUDP(pkt, serverAddr); err != nil { + encryptedPkt, err := shared.EncryptSym(pskKey, pkt) + if err != nil { + panic(err) + } + + req := shared.BuildPkt(shared.REQ_DATA, encryptedPkt) + if _, err := conn.WriteToUDP(req, serverAddr); err != nil { log.Println(err) } } diff --git a/go.mod b/go.mod index aa01da9..671c592 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,10 @@ module baalvpn go 1.26.3 require ( - github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 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 e213a41..a4f2d2e 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ -github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091 h1:1zN6ImoqhSJhN8hGXFaJlSC8msLmIbX8bFqOfWLKw0w= -github.com/songgao/packets v0.0.0-20160404182456-549a10cd4091/go.mod h1:N20Z5Y8oye9a7HmytmZ+tr8Q2vlP0tAHP13kTHzwvQY= 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= +golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= diff --git a/server/main.go b/server/main.go index c02a93e..d1b7902 100644 --- a/server/main.go +++ b/server/main.go @@ -1,12 +1,12 @@ // TODO: slice bounds checking // TODO: authentication -// TODO: encryption // TODO: somehow persist IPs // TODO: handle multiple peers behind one NAT package main import ( "crypto/rand" + "encoding/base64" "encoding/binary" "log" "net" @@ -21,10 +21,14 @@ type Peer struct { InternalIP string } +// 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() { @@ -32,6 +36,12 @@ func main() { panic("root permissions needed") } + var err error + pskKey, err = base64.StdEncoding.DecodeString(PSK) + if err != nil { + panic(err) + } + receivePackets() } @@ -59,25 +69,22 @@ func receivePackets() { continue } - go receivePacket(buffer[:n], addr) + go handleReq(buffer[:n], addr) } } -func receivePacket(pkt []byte, addr *net.UDPAddr) { - if len(pkt) < 4 { +func handleReq(req []byte, addr *net.UDPAddr) { + if len(req) < 4 { log.Println("packet too short") return } - version := binary.LittleEndian.Uint16(pkt[0:]) + version := binary.LittleEndian.Uint16(req[0:]) if version != shared.PROTO_VERSION { log.Println("mismatched packet version") return } - pktType := binary.LittleEndian.Uint16(pkt[2:]) - - // idk if it needs to lock the mutex - peer := peers[addr.IP.String()] + pktType := binary.LittleEndian.Uint16(req[2:]) switch pktType { case shared.REQ_REGISTER: @@ -98,15 +105,33 @@ func receivePacket(pkt []byte, addr *net.UDPAddr) { resp := shared.BuildPkt(shared.RESP_REGISTER, net.ParseIP(internalIP)) sendTo(addr, resp) case shared.REQ_DATA: - frame := pkt[4:] - etherType := binary.BigEndian.Uint16(frame[12:14]) - payload := frame[14:] + peersMutex.Lock() + peer := peers[addr.IP.String()] + peersMutex.Unlock() + if peer == nil { + log.Println("data from unregistered peer:", addr.String()) + return + } + + pkt, err := shared.DecryptSym(pskKey, req[4:]) + if err != nil { + log.Printf("failed to decrypt packet from %s: %s", peer.RealAddr, err) + 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() - resp := shared.BuildPkt(shared.RESP_DATA, frame) + + 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 + " (*)") diff --git a/shared/crypto.go b/shared/crypto.go new file mode 100644 index 0000000..83193f5 --- /dev/null +++ b/shared/crypto.go @@ -0,0 +1,36 @@ +package shared + +import ( + "crypto/rand" + "fmt" + + "golang.org/x/crypto/chacha20poly1305" +) + +func EncryptSym(key []byte, plaintext []byte) ([]byte, error) { + aead, err := chacha20poly1305.NewX(key) + if err != nil { + return nil, err + } + + nonce := make([]byte, chacha20poly1305.NonceSizeX) + if _, err := rand.Read(nonce); err != nil { + return nil, err + } + + return aead.Seal(nonce, nonce, plaintext, nil), nil +} + +func DecryptSym(key []byte, ciphertext []byte) ([]byte, error) { + aead, err := chacha20poly1305.NewX(key) + if err != nil { + return nil, err + } + + if len(ciphertext) < aead.NonceSize() { + return nil, fmt.Errorf("ciphertext too short") + } + nonce, ciphertext := ciphertext[:aead.NonceSize()], ciphertext[aead.NonceSize():] + + return aead.Open(nil, nonce, ciphertext, nil) +} diff --git a/shared/proto.go b/shared/proto.go index 7ee5da4..fc11a50 100644 --- a/shared/proto.go +++ b/shared/proto.go @@ -6,9 +6,13 @@ const PROTO_VERSION uint16 = 1 const ( unused uint16 = iota + // - empty - REQ_REGISTER + // [ ip - 16 bytes ] RESP_REGISTER + // [ encrypted pkt ] REQ_DATA + // [ encrypted pkt ] RESP_DATA )