wacky encryption
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
package shared
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
135
client/main.go
135
client/main.go
@@ -1,23 +1,24 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"crypto/sha256"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"time"
|
||||||
|
|
||||||
"baalvpn/shared"
|
"baalvpn/shared"
|
||||||
|
|
||||||
|
"filippo.io/mlkem768/xwing"
|
||||||
"github.com/songgao/water"
|
"github.com/songgao/water"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SERVER_IP = "172.20.12.47"
|
SERVER_IP = "172.20.12.47"
|
||||||
IFACE_NAME = "baalvpn"
|
IFACE_NAME = "baalvpn"
|
||||||
PSK = "rj5FvWB0n342C6UGrAS0gI3GX5ya+lDmC7/XmgFHF+s="
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -25,7 +26,14 @@ var (
|
|||||||
serverAddr *net.UDPAddr
|
serverAddr *net.UDPAddr
|
||||||
iface *water.Interface
|
iface *water.Interface
|
||||||
conn *net.UDPConn
|
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() {
|
func main() {
|
||||||
@@ -33,12 +41,6 @@ func main() {
|
|||||||
panic("root permissions needed")
|
panic("root permissions needed")
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
pskKey, err = base64.StdEncoding.DecodeString(PSK)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
listenAddr, err := net.ResolveUDPAddr("udp", ":38000")
|
listenAddr, err := net.ResolveUDPAddr("udp", ":38000")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -61,12 +63,63 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func register() {
|
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 {
|
if _, err := conn.WriteToUDP(req, serverAddr); err != nil {
|
||||||
panic(err)
|
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() {
|
func receivePackets() {
|
||||||
for {
|
for {
|
||||||
buffer := make([]byte, 50000)
|
buffer := make([]byte, 50000)
|
||||||
@@ -106,8 +159,12 @@ func receivePacket(pkt []byte) {
|
|||||||
log.Println("got assigned IP by the server:", internalIP.String())
|
log.Println("got assigned IP by the server:", internalIP.String())
|
||||||
setupInterface()
|
setupInterface()
|
||||||
go sendPackets()
|
go sendPackets()
|
||||||
case shared.RESP_DATA:
|
case shared.ENC_PKT:
|
||||||
decryptedPkt, err := shared.DecryptSym(pskKey, pkt[4:])
|
srcIP := net.IP(pkt[20:36]).String()
|
||||||
|
|
||||||
|
sessionKey := getOrEstablishSessionKey(srcIP)
|
||||||
|
|
||||||
|
decryptedPkt, err := DecryptSym(sessionKey, pkt[36:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -116,6 +173,37 @@ func receivePacket(pkt []byte) {
|
|||||||
if _, err := iface.Write(decryptedPkt); err != nil {
|
if _, err := iface.Write(decryptedPkt); err != nil {
|
||||||
log.Println(err)
|
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:
|
default:
|
||||||
log.Println("unknown packet type")
|
log.Println("unknown packet type")
|
||||||
}
|
}
|
||||||
@@ -150,14 +238,33 @@ func sendPackets() {
|
|||||||
}
|
}
|
||||||
pkt = pkt[:n]
|
pkt = pkt[:n]
|
||||||
|
|
||||||
encryptedPkt, err := shared.EncryptSym(pskKey, pkt)
|
etherType := binary.BigEndian.Uint16(pkt[12:14])
|
||||||
|
payload := pkt[14:]
|
||||||
|
|
||||||
|
if etherType != 0x86dd { // IPv6
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
req := shared.BuildPkt(shared.REQ_DATA, encryptedPkt)
|
reqBody := append(net.ParseIP(destIP), append(internalIP, encryptedPkt...)...)
|
||||||
|
req := shared.BuildPkt(shared.ENC_PKT, reqBody)
|
||||||
if _, err := conn.WriteToUDP(req, serverAddr); err != nil {
|
if _, err := conn.WriteToUDP(req, serverAddr); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
5
go.mod
5
go.mod
@@ -3,10 +3,9 @@ module baalvpn
|
|||||||
go 1.26.3
|
go 1.26.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
filippo.io/mlkem768 v0.0.0-20260214141301-2e7bebc7d88d
|
||||||
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
|
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
|
||||||
golang.org/x/crypto v0.54.0
|
golang.org/x/crypto v0.54.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require golang.org/x/sys v0.47.0 // indirect
|
||||||
golang.org/x/sys v0.47.0 // indirect
|
|
||||||
)
|
|
||||||
|
|||||||
2
go.sum
2
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 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8=
|
||||||
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
|
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 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
|
||||||
|
|||||||
@@ -2,11 +2,13 @@
|
|||||||
// TODO: authentication
|
// TODO: authentication
|
||||||
// TODO: somehow persist IPs
|
// TODO: somehow persist IPs
|
||||||
// TODO: handle multiple peers behind one NAT
|
// TODO: handle multiple peers behind one NAT
|
||||||
|
// TODO: key rotation
|
||||||
|
// TODO: sha256 -> HKDF
|
||||||
|
// TODO: recovery
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/base64"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
@@ -19,16 +21,13 @@ import (
|
|||||||
type Peer struct {
|
type Peer struct {
|
||||||
RealAddr *net.UDPAddr
|
RealAddr *net.UDPAddr
|
||||||
InternalIP string
|
InternalIP string
|
||||||
|
PublicKey []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: replace with key exchange between peers
|
|
||||||
const PSK = "rj5FvWB0n342C6UGrAS0gI3GX5ya+lDmC7/XmgFHF+s="
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
peers = map[string]*Peer{}
|
peers = map[string]*Peer{}
|
||||||
peersMutex sync.Mutex
|
peersMutex sync.Mutex
|
||||||
conn *net.UDPConn
|
conn *net.UDPConn
|
||||||
pskKey []byte
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -36,12 +35,6 @@ func main() {
|
|||||||
panic("root permissions needed")
|
panic("root permissions needed")
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
pskKey, err = base64.StdEncoding.DecodeString(PSK)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
receivePackets()
|
receivePackets()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,6 +84,8 @@ func handleReq(req []byte, addr *net.UDPAddr) {
|
|||||||
peersMutex.Lock()
|
peersMutex.Lock()
|
||||||
defer peersMutex.Unlock()
|
defer peersMutex.Unlock()
|
||||||
|
|
||||||
|
pubKey := req[4:]
|
||||||
|
|
||||||
internalIP := randomIP().String()
|
internalIP := randomIP().String()
|
||||||
|
|
||||||
if destPeer, ok := peers[addr.IP.String()]; ok {
|
if destPeer, ok := peers[addr.IP.String()]; ok {
|
||||||
@@ -99,12 +94,13 @@ func handleReq(req []byte, addr *net.UDPAddr) {
|
|||||||
peers[addr.IP.String()] = &Peer{
|
peers[addr.IP.String()] = &Peer{
|
||||||
RealAddr: addr,
|
RealAddr: addr,
|
||||||
InternalIP: internalIP,
|
InternalIP: internalIP,
|
||||||
|
PublicKey: pubKey,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := shared.BuildPkt(shared.RESP_REGISTER, net.ParseIP(internalIP))
|
resp := shared.BuildPkt(shared.RESP_REGISTER, net.ParseIP(internalIP))
|
||||||
sendTo(addr, resp)
|
sendTo(addr, resp)
|
||||||
case shared.REQ_DATA:
|
case shared.ENC_PKT:
|
||||||
peersMutex.Lock()
|
peersMutex.Lock()
|
||||||
peer := peers[addr.IP.String()]
|
peer := peers[addr.IP.String()]
|
||||||
peersMutex.Unlock()
|
peersMutex.Unlock()
|
||||||
@@ -113,38 +109,55 @@ func handleReq(req []byte, addr *net.UDPAddr) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pkt, err := shared.DecryptSym(pskKey, req[4:])
|
destIP := net.IP(req[4:20]).String()
|
||||||
if err != nil {
|
srcIP := net.IP(req[20:36]).String()
|
||||||
log.Printf("failed to decrypt packet from %s: %s", peer.RealAddr, err)
|
if srcIP != peer.InternalIP {
|
||||||
|
log.Println("rejected spoofed srcIP in ENC_PKT")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
etherType := binary.BigEndian.Uint16(pkt[12:14])
|
if destPeer := getPeerByInternalIP(destIP); destPeer != nil {
|
||||||
payload := pkt[14:]
|
log.Println(srcIP + " -> " + destIP)
|
||||||
|
sendTo(destPeer.RealAddr, req)
|
||||||
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 {
|
} else {
|
||||||
log.Println(peer.InternalIP + " -/> " + destIP + " (unrecognized IP)")
|
log.Println(srcIP + " -/> " + destIP + " (unrecognized IP)")
|
||||||
}
|
}
|
||||||
default:
|
case shared.BROADCAST_PKT:
|
||||||
log.Printf("unhandled EtherType: 0x%x\n", etherType)
|
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:
|
default:
|
||||||
log.Println("unknown packet type")
|
log.Println("unknown packet type")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,14 +6,30 @@ const PROTO_VERSION uint16 = 1
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
unused uint16 = iota
|
unused uint16 = iota
|
||||||
// - empty -
|
// (client -> server) requests an IP
|
||||||
|
// [ pubkey - 1216 bytes ]
|
||||||
REQ_REGISTER
|
REQ_REGISTER
|
||||||
|
// (server -> client) assigns an IP
|
||||||
// [ ip - 16 bytes ]
|
// [ ip - 16 bytes ]
|
||||||
RESP_REGISTER
|
RESP_REGISTER
|
||||||
// [ encrypted pkt ]
|
// (client -> server -> client2) relays an encrypted packet to a specified peer
|
||||||
REQ_DATA
|
// [ destIP - 16 bytes ] [ srcIP - 16 bytes ] [ encrypted pkt ]
|
||||||
// [ encrypted pkt ]
|
ENC_PKT
|
||||||
RESP_DATA
|
// (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 {
|
func BuildPkt(pktType uint16, data []byte) []byte {
|
||||||
|
|||||||
Reference in New Issue
Block a user