check inner packet srcIP against server-verified srcIP, Windows support

This commit is contained in:
2026-07-24 23:49:42 +02:00
parent 48811df6ed
commit 2b1de6460a
8 changed files with 219 additions and 51 deletions

View File

@@ -4,11 +4,19 @@ A hub-and-spoke VPN for IPv6
## Features ## Features
* **Post-quantum security:** Uses [HPKE](https://datatracker.ietf.org/doc/html/rfc9180/)([XWingMLKEM768X25519](https://datatracker.ietf.org/doc/html/draft-connolly-cfrg-xwing-kem/), [HKDF-SHA-256](https://en.wikipedia.org/wiki/HKDF)) + [XChaCha20-Poly1305](https://en.wikipedia.org/wiki/ChaCha20-Poly1305#XChaCha20-Poly1305_%E2%80%93_extended_nonce_variant) for encryption and [ML-DSA-44](https://nvlpubs.nist.gov/nistpubs/fips/nist.fips.204.pdf) for signatures. * **Post-quantum security:** Uses [HPKE](https://datatracker.ietf.org/doc/html/rfc9180/)([XWingMLKEM768X25519](https://datatracker.ietf.org/doc/html/draft-connolly-cfrg-xwing-kem/), [HKDF-SHA-256](https://en.wikipedia.org/wiki/HKDF)) + [XChaCha20-Poly1305](https://en.wikipedia.org/wiki/ChaCha20-Poly1305#XChaCha20-Poly1305_%E2%80%93_extended_nonce_variant) for encryption and [ML-DSA-44](https://nvlpubs.nist.gov/nistpubs/fips/nist.fips.204.pdf) for signatures.
* **NAT-resistant architecture:** Any device with Internet access can easily join the network.
* **Cross-platform-ish:** Tested on Linux and Windows. macOS should probably work too.
## Protocol ## Protocol
See [proto.go](https://git.ton1.dev/toni/baalvpn/src/branch/main/shared/proto.go) See [proto.go](https://git.ton1.dev/toni/baalvpn/src/branch/main/shared/proto.go) for details
### Known limitations
* Compromised server could give out fake pubkeys allowing a MITM attack and decrypting the peer-to-peer traffic
* The keys are rotated only on client and server restart
* The protocol is vulnerable to replay attacks
## Third-party dependencies ## Third-party dependencies
* [`github.com/songgao/water`](https://github.com/songgao/water) - cross-platform wrapper around TUN interfaces * [`github.com/songgao/water`](https://github.com/songgao/water) - wrapper around TUN interfaces for \*nix
* [`golang.zx2c4.com/wintun`](https://git.zx2c4.com/wintun-go) - wrapper around `wintun.dll`
* [`github.com/cloudflare/circl`](https://github.com/cloudflare/circl) - used for ML-DSA, hopefully [not for long](https://github.com/golang/go/issues/77626) * [`github.com/cloudflare/circl`](https://github.com/cloudflare/circl) - used for ML-DSA, hopefully [not for long](https://github.com/golang/go/issues/77626)

View File

@@ -8,26 +8,21 @@ import (
"encoding/hex" "encoding/hex"
"log" "log"
"net" "net"
"os"
"os/exec"
"sync" "sync"
"time" "time"
"baalvpn/shared" "baalvpn/shared"
"github.com/cloudflare/circl/sign/mldsa/mldsa44" "github.com/cloudflare/circl/sign/mldsa/mldsa44"
"github.com/songgao/water"
) )
// TODO: parse some sort of config+key file // TODO: parse some sort of config+key file
const ( const (
SERVER_IP = "172.20.12.47" SERVER_IP = "172.20.12.47"
IFACE_NAME = "baalvpn"
SERVER_PUBKEY = "b137ffedfaf45cb4dc5339373cdc935fbcc7597dde71b86112d282c2bda4c252e6676490acd3f3fd71a7ab7ce2edbe69521a966fa1e91056c8cd9f98443d49505c4a3663b90c322da0942e7511f9c600e0ebbeac071ba3763f623bfd2c2092123c0b2807518d70b790e2612b1b2daa32aa8fa14be8aef9072771e7c4e8c21b64173a504f92378804dcef63430b5c2d4a504d95a766b23ff04e804baf36b515d8c4116e9ffe5fa9afb1c2d005936b3e7222de604e8bae04fd5c56520d8e9dc428e6f6107fd735fcf54a7a324d8236d831b89b4fcda426d701add2b1f4122ba29021d263b74eea6ce1a546daab62b31b12c5b2f14a57ed1160af8630e1d2fa551e7609022a240ad487c76ef1c5b3632c7ca9d5c3648e8b0eaab2256ff5245b0dc25decd9271126b4834b13ddb365e75ad4411e73058460499c9a5522182007378aca990da89f87c90df379112586c90f42a2fe7ec1ab119fb95641abe61f7a04f95f2bd2a849cf01f561bcd4d5f4c57fff30e34e317bdbcba57126b068ded038a7947b6fd8501f2362fba7a695e628647ff28123b8a75df59da7b7b6a8064f6781fd70e62e86208942dc2fbe3a08999bc6b7e493dcf247d3679804c21ac0e64afd906d50b433a858ba6b55cfbb6668c5a4a7814345659756560a4e5678201a2cde4ca3c101520e0d1a0b06af5de5a596793e7bd68cdc45d2234e79b646fbb305096c193f053c6f158a0b1d2d590f43ba9236bb77000e1de75b2eb9693185bd82ac414db63450ea4808752f14946b8609d4b54c909b6854c8b2e822897e2497671102f41366808cd962802a128a551d6c56cccafb133c12294c14d97f02bc808c4a6777f1f602fe248d6d7ec6e850431d593a0510b3e5f75e19cafd3317b9951a12870d3ffdb6f4d0449ff63cce9a2acfd7eafc47befdd506b8b781133e4e4d364b816a072fa05b350368a62a61e4f63377897f579e21c67520c0edf949033210d7785a2435de37a0d609661b71d442bb11f8f9322f2df8dc52662de48299c8e3d82ab75124cb91e03f2d776ae33a6aed94d2eefa7a20d1fb9932c2511aa48304f703316e6397373418c1496b8be06cc97018af7bc43bf94cf5012c7486e251bfff3e275e759f7e1ca15afe8c1934c2a151d8cdd8c028fc67d3c6a22514d1aea6b907f130eda14860c559dee0dd88202ba2e00f5848777c2767bf96817c6b195c2556b4ce223397649c35f311de79c8687e3eec956a054bb36eb3024e520ec6d6455b12d44434ec4c41f7bcdd5133dc884de56ab77a8b1f50a613a5fbf90de87d30469dbc6d3c79b87e9a04a986bd00aa213e9cf270a37b50be919dcb999776f7928a7b43d700eeb6c92eaf901047607cb1d05839374aaa4eb49e8e7e4a69dc70a9b5b006284d72afa9a8f6a96473db5aff3c2f2ae2377a80570d2cd052cb2302d66c5115277673c9634117748c0ddf08315b17229a2ddaf238feb46270278de8d45b0c603a07b890d0f443f96c97a66bb2c783f43807e466e90aab185bcfc9e2036d1d745402c05913ee5bf380a99e5912612b10f39fa0dadefd2e2c47c67e4c118ea2d0579d35793fc2151bcbc40ebaa4e42b86231da9e952238d55327c8f9b96aec5bb1c4b283a6c67d0e0e715e754d8df84740920c002060781ac61662645ae3c92b4d79c28e7cc5e5103ff3b760e9d1feabed95b606275a57e1a1dc91a88505b41d1a4df434891d8e3f8531820a6397a5815c17dfde6e115061898f13611ecede2a1db94d2900359015e675244493f08228f95e36d9c004c0cb0405c2caed5cb31bd88800a6bdac7b53de67b36c2ea92ddd8496bafb0c304f458b28ef51217" SERVER_PUBKEY = "b137ffedfaf45cb4dc5339373cdc935fbcc7597dde71b86112d282c2bda4c252e6676490acd3f3fd71a7ab7ce2edbe69521a966fa1e91056c8cd9f98443d49505c4a3663b90c322da0942e7511f9c600e0ebbeac071ba3763f623bfd2c2092123c0b2807518d70b790e2612b1b2daa32aa8fa14be8aef9072771e7c4e8c21b64173a504f92378804dcef63430b5c2d4a504d95a766b23ff04e804baf36b515d8c4116e9ffe5fa9afb1c2d005936b3e7222de604e8bae04fd5c56520d8e9dc428e6f6107fd735fcf54a7a324d8236d831b89b4fcda426d701add2b1f4122ba29021d263b74eea6ce1a546daab62b31b12c5b2f14a57ed1160af8630e1d2fa551e7609022a240ad487c76ef1c5b3632c7ca9d5c3648e8b0eaab2256ff5245b0dc25decd9271126b4834b13ddb365e75ad4411e73058460499c9a5522182007378aca990da89f87c90df379112586c90f42a2fe7ec1ab119fb95641abe61f7a04f95f2bd2a849cf01f561bcd4d5f4c57fff30e34e317bdbcba57126b068ded038a7947b6fd8501f2362fba7a695e628647ff28123b8a75df59da7b7b6a8064f6781fd70e62e86208942dc2fbe3a08999bc6b7e493dcf247d3679804c21ac0e64afd906d50b433a858ba6b55cfbb6668c5a4a7814345659756560a4e5678201a2cde4ca3c101520e0d1a0b06af5de5a596793e7bd68cdc45d2234e79b646fbb305096c193f053c6f158a0b1d2d590f43ba9236bb77000e1de75b2eb9693185bd82ac414db63450ea4808752f14946b8609d4b54c909b6854c8b2e822897e2497671102f41366808cd962802a128a551d6c56cccafb133c12294c14d97f02bc808c4a6777f1f602fe248d6d7ec6e850431d593a0510b3e5f75e19cafd3317b9951a12870d3ffdb6f4d0449ff63cce9a2acfd7eafc47befdd506b8b781133e4e4d364b816a072fa05b350368a62a61e4f63377897f579e21c67520c0edf949033210d7785a2435de37a0d609661b71d442bb11f8f9322f2df8dc52662de48299c8e3d82ab75124cb91e03f2d776ae33a6aed94d2eefa7a20d1fb9932c2511aa48304f703316e6397373418c1496b8be06cc97018af7bc43bf94cf5012c7486e251bfff3e275e759f7e1ca15afe8c1934c2a151d8cdd8c028fc67d3c6a22514d1aea6b907f130eda14860c559dee0dd88202ba2e00f5848777c2767bf96817c6b195c2556b4ce223397649c35f311de79c8687e3eec956a054bb36eb3024e520ec6d6455b12d44434ec4c41f7bcdd5133dc884de56ab77a8b1f50a613a5fbf90de87d30469dbc6d3c79b87e9a04a986bd00aa213e9cf270a37b50be919dcb999776f7928a7b43d700eeb6c92eaf901047607cb1d05839374aaa4eb49e8e7e4a69dc70a9b5b006284d72afa9a8f6a96473db5aff3c2f2ae2377a80570d2cd052cb2302d66c5115277673c9634117748c0ddf08315b17229a2ddaf238feb46270278de8d45b0c603a07b890d0f443f96c97a66bb2c783f43807e466e90aab185bcfc9e2036d1d745402c05913ee5bf380a99e5912612b10f39fa0dadefd2e2c47c67e4c118ea2d0579d35793fc2151bcbc40ebaa4e42b86231da9e952238d55327c8f9b96aec5bb1c4b283a6c67d0e0e715e754d8df84740920c002060781ac61662645ae3c92b4d79c28e7cc5e5103ff3b760e9d1feabed95b606275a57e1a1dc91a88505b41d1a4df434891d8e3f8531820a6397a5815c17dfde6e115061898f13611ecede2a1db94d2900359015e675244493f08228f95e36d9c004c0cb0405c2caed5cb31bd88800a6bdac7b53de67b36c2ea92ddd8496bafb0c304f458b28ef51217"
) )
var ( var (
iface *water.Interface
conn *net.UDPConn conn *net.UDPConn
serverAddr *net.UDPAddr serverAddr *net.UDPAddr
internalIP net.IP internalIP net.IP
@@ -43,8 +38,8 @@ var (
) )
func main() { func main() {
if os.Geteuid() != 0 { if !isAdmin() {
panic("root permissions needed") panic("this program must be ran with administrative privileges")
} }
listenAddr, err := net.ResolveUDPAddr("udp", ":38000") listenAddr, err := net.ResolveUDPAddr("udp", ":38000")
@@ -157,23 +152,40 @@ func handleIncomingPkt(pkt []byte) {
srcIP := net.IP(pkt[52:68]).String() srcIP := net.IP(pkt[52:68]).String()
sessionKey := getOrEstablishSessionKey(srcIP) sessionKey := getOrEstablishSessionKey(srcIP)
if sessionKey == nil {
return
}
decryptedPkt, err := DecryptSym(sessionKey, pkt[68:]) decryptedPkt, err := DecryptSym(sessionKey, pkt[68:])
if err != nil { if err != nil {
panic(err) panic(err)
} }
if len(decryptedPkt) >= 24 {
innerSrcIP := net.IP(decryptedPkt[8:24]).String()
if innerSrcIP != srcIP {
return
}
}
log.Println("received pkt:", hex.EncodeToString(decryptedPkt)) log.Println("received pkt:", hex.EncodeToString(decryptedPkt))
if _, err := iface.Write(decryptedPkt); err != nil { if err := writePkt(decryptedPkt); err != nil {
log.Println(err) log.Println(err)
} }
case shared.BROADCAST_PKT: case shared.BROADCAST_PKT:
decryptedPkt, err := DecryptSym(multicastKey, pkt[36:]) decryptedPkt, err := DecryptSym(multicastKey, pkt[52:])
if err != nil { if err != nil {
panic(err) 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)) log.Println("received broadcast:", hex.EncodeToString(decryptedPkt))
if _, err := iface.Write(decryptedPkt); err != nil { if err := writePkt(decryptedPkt); err != nil {
log.Println(err) log.Println(err)
} }
case shared.RESP_GET_PUBKEY: case shared.RESP_GET_PUBKEY:
@@ -226,9 +238,10 @@ func getOrEstablishSessionKey(ip string) []byte {
return key return key
} }
log.Println("lets try to get " + ip + "'s pubkey")
// TODO: this definitely shouldnt block the main thread // 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 range 30 {
for {
// request pubkey every 50ms // request pubkey every 50ms
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
peerPubKey, ok := peerPubKeys.GetOK(ip) peerPubKey, ok := peerPubKeys.GetOK(ip)
@@ -250,51 +263,41 @@ func getOrEstablishSessionKey(ip string) []byte {
send(buildAuthPkt(shared.REQ_ESTABLISH, net.ParseIP(ip), internalIP, ciphertext)) send(buildAuthPkt(shared.REQ_ESTABLISH, net.ParseIP(ip), internalIP, ciphertext))
for { success := false
for range 30 {
// TODO: eww // TODO: eww
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
if peerEstablishAcks.Get(ip) { if peerEstablishAcks.Get(ip) {
peerEstablishAcks.Delete(ip) peerEstablishAcks.Delete(ip)
success = true
break break
} }
} }
if !success {
log.Println("failed to establish")
return nil
}
peerSessionKeys.Set(ip, sessionKey) peerSessionKeys.Set(ip, sessionKey)
return sessionKey return sessionKey
} }
}
func setupInterface() { log.Println("failed to get the pubkey")
config := water.Config{DeviceType: water.TUN} return nil
config.Name = IFACE_NAME
var err error
iface, err = water.New(config)
if err != nil {
panic(err)
}
if err := exec.Command("ip", "addr", "add", internalIP.String()+"/32", "dev", IFACE_NAME).Run(); err != nil {
panic(err)
}
if err := exec.Command("ip", "link", "set", "dev", IFACE_NAME, "up").Run(); err != nil {
panic(err)
}
} }
func relayPackets() { func relayPackets() {
log.Println("Listening for packets...") log.Println("Listening for packets...")
for { for {
pkt := make([]byte, 2000) pkt, err := readPkt()
n, err := iface.Read([]byte(pkt))
if err != nil { if err != nil {
panic(err) panic(err)
} }
pkt = pkt[:n]
version := pkt[0] >> 4 version := pkt[0] >> 4
if version != 6 { if version != 6 {
releasePkt(pkt)
continue continue
} }
@@ -306,9 +309,13 @@ func relayPackets() {
panic(err) panic(err)
} }
send(buildAuthPkt(shared.BROADCAST_PKT, encryptedPkt)) send(buildAuthPkt(shared.BROADCAST_PKT, internalIP, encryptedPkt))
} else { } else {
destSessionKey := getOrEstablishSessionKey(destIP) destSessionKey := getOrEstablishSessionKey(destIP)
if destSessionKey == nil {
releasePkt(pkt)
continue
}
encryptedPkt, err := EncryptSym(destSessionKey, pkt) encryptedPkt, err := EncryptSym(destSessionKey, pkt)
if err != nil { if err != nil {
@@ -317,6 +324,8 @@ func relayPackets() {
send(buildAuthPkt(shared.UNICAST_PKT, net.ParseIP(destIP), internalIP, encryptedPkt)) send(buildAuthPkt(shared.UNICAST_PKT, net.ParseIP(destIP), internalIP, encryptedPkt))
} }
releasePkt(pkt)
} }
} }

61
client/os.go Normal file
View File

@@ -0,0 +1,61 @@
//go:build linux || darwin
package main
import (
"os"
"os/exec"
"runtime"
"github.com/songgao/water"
)
var iface *water.Interface
func isAdmin() bool {
return os.Geteuid() == 0
}
func setupInterface() {
config := water.Config{DeviceType: water.TUN}
if runtime.GOOS == "linux" {
config.Name = "baalvpn"
} else {
panic("TODO")
}
var err error
iface, err = water.New(config)
if err != nil {
panic(err)
}
if runtime.GOOS == "linux" {
if err := exec.Command("ip", "addr", "add", internalIP.String()+"/32", "dev", "baalvpn").Run(); err != nil {
panic(err)
}
if err := exec.Command("ip", "link", "set", "dev", "baalvpn", "up").Run(); err != nil {
panic(err)
}
} else {
if err := exec.Command("ifconfig", iface.Name(), "inet6", internalIP.String(), internalIP.String(), "prefixlen", "128", "up").Run(); err != nil {
panic(err)
}
}
}
func writePkt(pkt []byte) error {
_, err := iface.Write(pkt)
return err
}
func readPkt() ([]byte, error) {
pkt := make([]byte, 2000)
n, err := iface.Read(pkt)
if err != nil {
return nil, err
}
return pkt[:n], nil
}
func releasePkt(pkt []byte) {}

76
client/os_windows.go Normal file
View File

@@ -0,0 +1,76 @@
//go:build windows
package main
import (
"os"
"os/exec"
"golang.org/x/sys/windows"
"golang.zx2c4.com/wintun"
)
var session wintun.Session
var readWait windows.Handle
func isAdmin() bool {
f, err := os.Open("\\\\.\\PHYSICALDRIVE0")
if err != nil {
return false
}
f.Close()
return true
}
func setupInterface() {
adapter, err := wintun.CreateAdapter("baalvpn", "Wintun", nil)
if err != nil {
panic(err)
}
session, err = adapter.StartSession(0x800000)
if err != nil {
panic(err)
}
readWait = session.ReadWaitEvent()
if err := exec.Command("netsh", "interface", "ipv6", "add", "address", "interface=baalvpn", "address="+internalIP.String()).Run(); err != nil {
panic(err)
}
if err := exec.Command("netsh", "interface", "ipv6", "set", "interface", "interface=baalvpn", "forwarding=disabled").Run(); err != nil {
panic(err)
}
if err := exec.Command("netsh", "interface", "ipv6", "add", "route", "fd00:baa1::/32", "interface=baalvpn").Run(); err != nil {
panic(err)
}
if err := exec.Command("netsh", "advfirewall", "firewall", "add", "rule", "name=BaalVPN In", "dir=in", "action=allow", "remoteip=fd00:baa1::/32").Run(); err != nil {
panic(err)
}
if err := exec.Command("netsh", "advfirewall", "firewall", "add", "rule", "name=BaalVPN Out", "dir=out", "action=allow", "remoteip=fd00:baa1::/32").Run(); err != nil {
panic(err)
}
}
func writePkt(pkt []byte) error {
sp, err := session.AllocateSendPacket(len(pkt))
if err != nil {
return err
}
copy(sp, pkt)
session.SendPacket(sp)
return nil
}
func readPkt() ([]byte, error) {
for {
pkt, err := session.ReceivePacket()
if err == nil {
return pkt, nil
}
windows.WaitForSingleObject(readWait, windows.INFINITE)
}
}
func releasePkt(pkt []byte) {
session.ReleaseReceivePacket(pkt)
}

4
go.mod
View File

@@ -6,6 +6,6 @@ require (
github.com/cloudflare/circl v1.6.4 github.com/cloudflare/circl v1.6.4
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
golang.org/x/sys v0.47.0
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
) )
require golang.org/x/sys v0.47.0 // indirect

2
go.sum
View File

@@ -6,3 +6,5 @@ golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= 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 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=

View File

@@ -217,7 +217,7 @@ func handleReq(req []byte, addr *net.UDPAddr) {
log.Println(srcIP + " -/> " + destIP + " (unrecognized IP)") log.Println(srcIP + " -/> " + destIP + " (unrecognized IP)")
} }
case shared.BROADCAST_PKT: case shared.BROADCAST_PKT:
if len(req) <= 36 { if len(req) <= 52 {
log.Println("BROADCAST_PKT packet too short") log.Println("BROADCAST_PKT packet too short")
return return
} }
@@ -227,6 +227,12 @@ func handleReq(req []byte, addr *net.UDPAddr) {
return return
} }
srcIP := net.IP(req[36:52]).String()
if srcIP != peer.InternalIP {
log.Println("rejected spoofed srcIP in BROADCAST_PKT")
return
}
mac := hmac.New(sha256.New, peer.AuthSecret) mac := hmac.New(sha256.New, peer.AuthSecret)
mac.Write(req[36:]) mac.Write(req[36:])
if !hmac.Equal(mac.Sum(nil), req[4:36]) { if !hmac.Equal(mac.Sum(nil), req[4:36]) {
@@ -241,9 +247,12 @@ func handleReq(req []byte, addr *net.UDPAddr) {
log.Println("REQ_GET_PUBKEY packet too short") log.Println("REQ_GET_PUBKEY packet too short")
return return
} }
peerIP := net.IP(req[36:52]).String()
peer := peersByInternal.Get(peerIP) peer := peers.Get(addr.String())
if peer != nil { if peer == nil {
log.Println("REQ_GET_PUBKEY from unregistered peer:", addr.String())
return
}
mac := hmac.New(sha256.New, peer.AuthSecret) mac := hmac.New(sha256.New, peer.AuthSecret)
mac.Write(req[36:]) mac.Write(req[36:])
if !hmac.Equal(mac.Sum(nil), req[4:36]) { if !hmac.Equal(mac.Sum(nil), req[4:36]) {
@@ -251,7 +260,10 @@ func handleReq(req []byte, addr *net.UDPAddr) {
return return
} }
pubKey, err := hex.DecodeString(peer.HexPublicKey) peerIP := net.IP(req[36:52]).String()
targetPeer := peersByInternal.Get(peerIP)
if targetPeer != nil {
pubKey, err := hex.DecodeString(targetPeer.HexPublicKey)
if err != nil { if err != nil {
panic(err) // should never happen panic(err) // should never happen
} }

View File

@@ -12,7 +12,7 @@ const PROTO_VERSION uint16 = 1
// multicastKey - key shared between everyone for encrypting multicast packets // multicastKey - key shared between everyone for encrypting multicast packets
// authSecret - secret assigned to a client for authenticating subsequent requests // authSecret - secret assigned to a client for authenticating subsequent requests
// SYM = XChaCha20Poly1305 with nonce prepended // SYM = XChaCha20-Poly1305 with nonce prepended
// HMAC = HMAC-SHA-256 // HMAC = HMAC-SHA-256
// hpke = HPKE(MLKEM768X25519, ChaCha20Poly1305, HKDF-SHA-256) // hpke = HPKE(MLKEM768X25519, ChaCha20Poly1305, HKDF-SHA-256)
// ds = ML-DSA // ds = ML-DSA
@@ -49,7 +49,7 @@ const (
// [ HMAC(authSecret, rest) - 32 bytes ] [ destIP - 16 bytes ] [ srcIP - 16 bytes ] [ SYM(sessionKey, pkt) ] // [ HMAC(authSecret, rest) - 32 bytes ] [ destIP - 16 bytes ] [ srcIP - 16 bytes ] [ SYM(sessionKey, pkt) ]
UNICAST_PKT UNICAST_PKT
// (client -> server -> *) broadcasts an encrypted packet // (client -> server -> *) broadcasts an encrypted packet
// [ HMAC(authSecret, rest) - 32 bytes ] [ SYM(multicastKey, pkt) ] // [ HMAC(authSecret, rest) - 32 bytes ] [ srcIP - 16 bytes ] [ SYM(multicastKey, pkt) ]
BROADCAST_PKT BROADCAST_PKT
) )