Files
baalvpn/client/main.go

359 lines
10 KiB
Go

package main
import (
"crypto/hmac"
"crypto/hpke"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"log"
"net"
"sync"
"time"
"baalvpn/shared"
"github.com/cloudflare/circl/sign/mldsa/mldsa44"
)
// TODO: parse some sort of config+key file
const (
SERVER_IP = "172.20.12.47"
SERVER_PUBKEY = "b137ffedfaf45cb4dc5339373cdc935fbcc7597dde71b86112d282c2bda4c252e6676490acd3f3fd71a7ab7ce2edbe69521a966fa1e91056c8cd9f98443d49505c4a3663b90c322da0942e7511f9c600e0ebbeac071ba3763f623bfd2c2092123c0b2807518d70b790e2612b1b2daa32aa8fa14be8aef9072771e7c4e8c21b64173a504f92378804dcef63430b5c2d4a504d95a766b23ff04e804baf36b515d8c4116e9ffe5fa9afb1c2d005936b3e7222de604e8bae04fd5c56520d8e9dc428e6f6107fd735fcf54a7a324d8236d831b89b4fcda426d701add2b1f4122ba29021d263b74eea6ce1a546daab62b31b12c5b2f14a57ed1160af8630e1d2fa551e7609022a240ad487c76ef1c5b3632c7ca9d5c3648e8b0eaab2256ff5245b0dc25decd9271126b4834b13ddb365e75ad4411e73058460499c9a5522182007378aca990da89f87c90df379112586c90f42a2fe7ec1ab119fb95641abe61f7a04f95f2bd2a849cf01f561bcd4d5f4c57fff30e34e317bdbcba57126b068ded038a7947b6fd8501f2362fba7a695e628647ff28123b8a75df59da7b7b6a8064f6781fd70e62e86208942dc2fbe3a08999bc6b7e493dcf247d3679804c21ac0e64afd906d50b433a858ba6b55cfbb6668c5a4a7814345659756560a4e5678201a2cde4ca3c101520e0d1a0b06af5de5a596793e7bd68cdc45d2234e79b646fbb305096c193f053c6f158a0b1d2d590f43ba9236bb77000e1de75b2eb9693185bd82ac414db63450ea4808752f14946b8609d4b54c909b6854c8b2e822897e2497671102f41366808cd962802a128a551d6c56cccafb133c12294c14d97f02bc808c4a6777f1f602fe248d6d7ec6e850431d593a0510b3e5f75e19cafd3317b9951a12870d3ffdb6f4d0449ff63cce9a2acfd7eafc47befdd506b8b781133e4e4d364b816a072fa05b350368a62a61e4f63377897f579e21c67520c0edf949033210d7785a2435de37a0d609661b71d442bb11f8f9322f2df8dc52662de48299c8e3d82ab75124cb91e03f2d776ae33a6aed94d2eefa7a20d1fb9932c2511aa48304f703316e6397373418c1496b8be06cc97018af7bc43bf94cf5012c7486e251bfff3e275e759f7e1ca15afe8c1934c2a151d8cdd8c028fc67d3c6a22514d1aea6b907f130eda14860c559dee0dd88202ba2e00f5848777c2767bf96817c6b195c2556b4ce223397649c35f311de79c8687e3eec956a054bb36eb3024e520ec6d6455b12d44434ec4c41f7bcdd5133dc884de56ab77a8b1f50a613a5fbf90de87d30469dbc6d3c79b87e9a04a986bd00aa213e9cf270a37b50be919dcb999776f7928a7b43d700eeb6c92eaf901047607cb1d05839374aaa4eb49e8e7e4a69dc70a9b5b006284d72afa9a8f6a96473db5aff3c2f2ae2377a80570d2cd052cb2302d66c5115277673c9634117748c0ddf08315b17229a2ddaf238feb46270278de8d45b0c603a07b890d0f443f96c97a66bb2c783f43807e466e90aab185bcfc9e2036d1d745402c05913ee5bf380a99e5912612b10f39fa0dadefd2e2c47c67e4c118ea2d0579d35793fc2151bcbc40ebaa4e42b86231da9e952238d55327c8f9b96aec5bb1c4b283a6c67d0e0e715e754d8df84740920c002060781ac61662645ae3c92b4d79c28e7cc5e5103ff3b760e9d1feabed95b606275a57e1a1dc91a88505b41d1a4df434891d8e3f8531820a6397a5815c17dfde6e115061898f13611ecede2a1db94d2900359015e675244493f08228f95e36d9c004c0cb0405c2caed5cb31bd88800a6bdac7b53de67b36c2ea92ddd8496bafb0c304f458b28ef51217"
)
var (
conn *net.UDPConn
serverAddr *net.UDPAddr
internalIP net.IP
privKey hpke.PrivateKey
multicastKey []byte
challengeCh = make(chan []byte, 1)
authSecret []byte
establishMutex sync.Mutex
peerPubKeys = shared.NewTMap[string, hpke.PublicKey]()
peerSessionKeys = shared.NewTMap[string, []byte]()
peerEstablishAcks = shared.NewTMap[string, bool]()
)
func main() {
if !isAdmin() {
panic("this program must be ran with administrative privileges")
}
listenAddr, err := net.ResolveUDPAddr("udp", ":38000")
if err != nil {
panic(err)
}
conn, err = net.ListenUDP("udp", listenAddr)
if err != nil {
panic(err)
}
defer conn.Close()
serverAddr, err = net.ResolveUDPAddr("udp", SERVER_IP+":38000")
if err != nil {
panic(err)
}
go receivePackets()
register()
<-make(chan int) // block forever
}
func register() {
var err error
privKey, err = hpke.MLKEM768X25519().GenerateKey()
if err != nil {
panic(err)
}
pubKey := privKey.PublicKey()
// TODO: all this probably should be retried until we get RESP_REGISTER
log.Println("requesting authSecret")
send(shared.BuildPkt(shared.REQ_GET_CHALLENGE, pubKey.Bytes()))
challenge := <-challengeCh
authSecret, err = hpke.Open(privKey, hpke.HKDFSHA256(), hpke.ChaCha20Poly1305(), []byte("baalvpn-challenge"), challenge)
if err != nil {
panic(err)
}
log.Println("received authSecret, registering...")
mac := hmac.New(sha256.New, authSecret)
mac.Write(pubKey.Bytes())
send(shared.BuildPkt(shared.REQ_REGISTER, mac.Sum(nil), pubKey.Bytes()))
}
func receivePackets() {
buffer := make([]byte, 50000)
for {
n, addr, err := conn.ReadFromUDP(buffer[:])
if err != nil {
log.Println(err)
continue
}
if n == 0 {
continue
}
if addr.String() != serverAddr.String() {
log.Println("non-server connection rejected")
continue
}
req := make([]byte, n)
copy(req, buffer[:n])
go handleIncomingPkt(req)
}
}
func handleIncomingPkt(pkt []byte) {
if len(pkt) <= 4 {
log.Println("packet too short")
return
}
version := binary.LittleEndian.Uint16(pkt[0:])
if version != shared.PROTO_VERSION {
log.Println("mismatched packet version")
return
}
pktType := binary.LittleEndian.Uint16(pkt[2:])
switch pktType {
case shared.RESP_GET_CHALLENGE:
if !verifyPkt(pkt) {
log.Println("failed to verify signature")
return
}
challengeCh <- pkt[2424:]
case shared.RESP_REGISTER:
if !verifyPkt(pkt) {
log.Println("failed to verify signature")
return
}
internalIP = net.IP(pkt[2424:2440])
var err error
multicastKey, err = hpke.Open(privKey, hpke.HKDFSHA256(), hpke.ChaCha20Poly1305(), []byte("baalvpn-register"), pkt[2440:])
if err != nil {
panic(err)
}
log.Println("got assigned IP by the server:", internalIP.String())
setupInterface()
go relayPackets()
case shared.UNICAST_PKT:
srcIP := net.IP(pkt[52:68]).String()
sessionKey := getOrEstablishSessionKey(srcIP)
if sessionKey == nil {
return
}
decryptedPkt, err := DecryptSym(sessionKey, pkt[68:])
if err != nil {
panic(err)
}
if len(decryptedPkt) >= 24 {
innerSrcIP := net.IP(decryptedPkt[8:24]).String()
if innerSrcIP != srcIP {
return
}
}
log.Println("received pkt:", hex.EncodeToString(decryptedPkt))
if err := writePkt(decryptedPkt); err != nil {
log.Println(err)
}
case shared.BROADCAST_PKT:
decryptedPkt, err := DecryptSym(multicastKey, pkt[52:])
if err != nil {
panic(err)
}
if len(decryptedPkt) >= 24 {
outerSrcIP := net.IP(pkt[36:52]).String()
innerSrcIP := net.IP(decryptedPkt[8:24]).String()
if innerSrcIP != outerSrcIP {
return
}
}
log.Println("received broadcast:", hex.EncodeToString(decryptedPkt))
if err := writePkt(decryptedPkt); err != nil {
log.Println(err)
}
case shared.RESP_GET_PUBKEY:
if !verifyPkt(pkt) {
log.Println("failed to verify signature")
return
}
respIP := net.IP(pkt[2424:2440]).String()
pubKey, err := hpke.MLKEM768X25519().NewPublicKey(pkt[2440:])
if err != nil {
panic(err)
}
peerPubKeys.Set(respIP, pubKey)
case shared.REQ_ESTABLISH:
srcIP := net.IP(pkt[52:68]).String()
ciphertext := pkt[68:]
r, err := hpke.NewRecipient(ciphertext, privKey, hpke.HKDFSHA256(), hpke.ExportOnly(), nil)
if err != nil {
panic(err)
}
sessionKey, err := r.Export("baalvpn-establish", 32)
if err != nil {
panic(err)
}
log.Println("received session key from", srcIP)
peerSessionKeys.Set(srcIP, sessionKey)
send(buildAuthPkt(shared.RESP_ESTABLISH, net.ParseIP(srcIP), internalIP))
case shared.RESP_ESTABLISH:
srcIP := net.IP(pkt[52:68]).String()
log.Println("established session key with", srcIP)
peerEstablishAcks.Set(srcIP, true)
default:
log.Println("unknown packet type")
}
}
func getOrEstablishSessionKey(ip string) []byte {
if key, ok := peerSessionKeys.GetOK(ip); ok {
return key
}
establishMutex.Lock()
defer establishMutex.Unlock()
// check again after the other goroutine finished
if key, ok := peerSessionKeys.GetOK(ip); ok {
return key
}
log.Println("lets try to get " + ip + "'s pubkey")
// TODO: this definitely shouldnt block the main thread
for range 30 {
// request pubkey every 50ms
time.Sleep(50 * time.Millisecond)
peerPubKey, ok := peerPubKeys.GetOK(ip)
if !ok {
send(buildAuthPkt(shared.REQ_GET_PUBKEY, net.ParseIP(ip)))
continue
}
log.Println("got pubkey of " + ip)
ciphertext, sender, err := hpke.NewSender(peerPubKey, hpke.HKDFSHA256(), hpke.ExportOnly(), nil)
if err != nil {
panic(err)
}
sessionKey, err := sender.Export("baalvpn-establish", 32)
if err != nil {
panic(err)
}
send(buildAuthPkt(shared.REQ_ESTABLISH, net.ParseIP(ip), internalIP, ciphertext))
success := false
for range 30 {
// TODO: eww
time.Sleep(50 * time.Millisecond)
if peerEstablishAcks.Get(ip) {
peerEstablishAcks.Delete(ip)
success = true
break
}
}
if !success {
log.Println("failed to establish")
return nil
}
peerSessionKeys.Set(ip, sessionKey)
return sessionKey
}
log.Println("failed to get the pubkey")
return nil
}
func relayPackets() {
log.Println("Listening for packets...")
for {
pkt, err := readPkt()
if err != nil {
panic(err)
}
version := pkt[0] >> 4
if version != 6 {
releasePkt(pkt)
continue
}
destIP := net.IP(pkt[24:40]).String()
if pkt[24] == 0xff { // multicast
encryptedPkt, err := EncryptSym(multicastKey, pkt)
if err != nil {
panic(err)
}
send(buildAuthPkt(shared.BROADCAST_PKT, internalIP, encryptedPkt))
} else {
destSessionKey := getOrEstablishSessionKey(destIP)
if destSessionKey == nil {
releasePkt(pkt)
continue
}
encryptedPkt, err := EncryptSym(destSessionKey, pkt)
if err != nil {
panic(err)
}
send(buildAuthPkt(shared.UNICAST_PKT, net.ParseIP(destIP), internalIP, encryptedPkt))
}
releasePkt(pkt)
}
}
func buildAuthPkt(pktType uint16, parts ...[]byte) []byte {
pkt := shared.BuildPkt(pktType, parts...)
mac := hmac.New(sha256.New, authSecret)
// this ignores version and packet type, fine for now
mac.Write(pkt[4:])
return append(pkt[:4], append(mac.Sum(nil), pkt[4:]...)...)
}
func send(req []byte) {
if _, err := conn.WriteToUDP(req, serverAddr); err != nil {
panic(err)
}
}
func verifyPkt(pkt []byte) bool {
pubKeyBytes, err := hex.DecodeString(SERVER_PUBKEY)
if err != nil {
panic(err) // fatal misconfiguration
}
var pubKey mldsa44.PublicKey
if err := pubKey.UnmarshalBinary(pubKeyBytes); err != nil {
panic(err) // fatal misconfiguration
}
// this ignores version and packet type, fine for now
return mldsa44.Verify(&pubKey, pkt[2424:], nil, pkt[4:2424])
}