prevent mitm by signing server messages
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
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
|
||||
* **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.
|
||||
|
||||
## Protocol
|
||||
|
||||
@@ -11,3 +11,4 @@ See [proto.go](https://git.ton1.dev/toni/baalvpn/src/branch/main/shared/proto.go
|
||||
|
||||
## Third-party dependencies
|
||||
* [`github.com/songgao/water`](https://github.com/songgao/water) - cross-platform wrapper around TUN interfaces
|
||||
* [`github.com/cloudflare/circl`](https://github.com/cloudflare/circl) - used for ML-DSA, hopefully [not for long](https://github.com/golang/go/issues/77626)
|
||||
|
||||
@@ -15,13 +15,15 @@ import (
|
||||
|
||||
"baalvpn/shared"
|
||||
|
||||
"github.com/cloudflare/circl/sign/mldsa/mldsa44"
|
||||
"github.com/songgao/water"
|
||||
)
|
||||
|
||||
// TODO: parse some sort of config+key file
|
||||
const (
|
||||
SERVER_IP = "172.20.12.47"
|
||||
IFACE_NAME = "baalvpn"
|
||||
SERVER_IP = "172.20.12.47"
|
||||
IFACE_NAME = "baalvpn"
|
||||
SERVER_PUBKEY = "b137ffedfaf45cb4dc5339373cdc935fbcc7597dde71b86112d282c2bda4c252e6676490acd3f3fd71a7ab7ce2edbe69521a966fa1e91056c8cd9f98443d49505c4a3663b90c322da0942e7511f9c600e0ebbeac071ba3763f623bfd2c2092123c0b2807518d70b790e2612b1b2daa32aa8fa14be8aef9072771e7c4e8c21b64173a504f92378804dcef63430b5c2d4a504d95a766b23ff04e804baf36b515d8c4116e9ffe5fa9afb1c2d005936b3e7222de604e8bae04fd5c56520d8e9dc428e6f6107fd735fcf54a7a324d8236d831b89b4fcda426d701add2b1f4122ba29021d263b74eea6ce1a546daab62b31b12c5b2f14a57ed1160af8630e1d2fa551e7609022a240ad487c76ef1c5b3632c7ca9d5c3648e8b0eaab2256ff5245b0dc25decd9271126b4834b13ddb365e75ad4411e73058460499c9a5522182007378aca990da89f87c90df379112586c90f42a2fe7ec1ab119fb95641abe61f7a04f95f2bd2a849cf01f561bcd4d5f4c57fff30e34e317bdbcba57126b068ded038a7947b6fd8501f2362fba7a695e628647ff28123b8a75df59da7b7b6a8064f6781fd70e62e86208942dc2fbe3a08999bc6b7e493dcf247d3679804c21ac0e64afd906d50b433a858ba6b55cfbb6668c5a4a7814345659756560a4e5678201a2cde4ca3c101520e0d1a0b06af5de5a596793e7bd68cdc45d2234e79b646fbb305096c193f053c6f158a0b1d2d590f43ba9236bb77000e1de75b2eb9693185bd82ac414db63450ea4808752f14946b8609d4b54c909b6854c8b2e822897e2497671102f41366808cd962802a128a551d6c56cccafb133c12294c14d97f02bc808c4a6777f1f602fe248d6d7ec6e850431d593a0510b3e5f75e19cafd3317b9951a12870d3ffdb6f4d0449ff63cce9a2acfd7eafc47befdd506b8b781133e4e4d364b816a072fa05b350368a62a61e4f63377897f579e21c67520c0edf949033210d7785a2435de37a0d609661b71d442bb11f8f9322f2df8dc52662de48299c8e3d82ab75124cb91e03f2d776ae33a6aed94d2eefa7a20d1fb9932c2511aa48304f703316e6397373418c1496b8be06cc97018af7bc43bf94cf5012c7486e251bfff3e275e759f7e1ca15afe8c1934c2a151d8cdd8c028fc67d3c6a22514d1aea6b907f130eda14860c559dee0dd88202ba2e00f5848777c2767bf96817c6b195c2556b4ce223397649c35f311de79c8687e3eec956a054bb36eb3024e520ec6d6455b12d44434ec4c41f7bcdd5133dc884de56ab77a8b1f50a613a5fbf90de87d30469dbc6d3c79b87e9a04a986bd00aa213e9cf270a37b50be919dcb999776f7928a7b43d700eeb6c92eaf901047607cb1d05839374aaa4eb49e8e7e4a69dc70a9b5b006284d72afa9a8f6a96473db5aff3c2f2ae2377a80570d2cd052cb2302d66c5115277673c9634117748c0ddf08315b17229a2ddaf238feb46270278de8d45b0c603a07b890d0f443f96c97a66bb2c783f43807e466e90aab185bcfc9e2036d1d745402c05913ee5bf380a99e5912612b10f39fa0dadefd2e2c47c67e4c118ea2d0579d35793fc2151bcbc40ebaa4e42b86231da9e952238d55327c8f9b96aec5bb1c4b283a6c67d0e0e715e754d8df84740920c002060781ac61662645ae3c92b4d79c28e7cc5e5103ff3b760e9d1feabed95b606275a57e1a1dc91a88505b41d1a4df434891d8e3f8531820a6397a5815c17dfde6e115061898f13611ecede2a1db94d2900359015e675244493f08228f95e36d9c004c0cb0405c2caed5cb31bd88800a6bdac7b53de67b36c2ea92ddd8496bafb0c304f458b28ef51217"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -76,18 +78,18 @@ func register() {
|
||||
|
||||
// TODO: all this probably should be retried until we get RESP_REGISTER
|
||||
|
||||
log.Println("requesting register challenge")
|
||||
log.Println("requesting authSecret")
|
||||
send(shared.BuildPkt(shared.REQ_GET_CHALLENGE, pubKey.Bytes()))
|
||||
|
||||
challenge := <-challengeCh
|
||||
log.Println("got register challenge")
|
||||
|
||||
authSecret, err = hpke.Open(privKey, hpke.HKDFSHA256(), hpke.ChaCha20Poly1305(), []byte("baalvpn-challenge"), challenge)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
log.Println("registering")
|
||||
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()))
|
||||
@@ -136,12 +138,20 @@ func handleIncomingPkt(pkt []byte) {
|
||||
|
||||
switch pktType {
|
||||
case shared.RESP_GET_CHALLENGE:
|
||||
challengeCh <- pkt[4:]
|
||||
if !verifyPacket(pkt) {
|
||||
log.Println("failed to verify signature")
|
||||
return
|
||||
}
|
||||
challengeCh <- pkt[2424:]
|
||||
case shared.RESP_REGISTER:
|
||||
internalIP = net.IP(pkt[4:20])
|
||||
if !verifyPacket(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[20:])
|
||||
multicastKey, err = hpke.Open(privKey, hpke.HKDFSHA256(), hpke.ChaCha20Poly1305(), []byte("baalvpn-register"), pkt[2440:])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -149,7 +159,7 @@ func handleIncomingPkt(pkt []byte) {
|
||||
log.Println("got assigned IP by the server:", internalIP.String())
|
||||
setupInterface()
|
||||
go relayPackets()
|
||||
case shared.ENC_PKT:
|
||||
case shared.UNICAST_PKT:
|
||||
srcIP := net.IP(pkt[52:68]).String()
|
||||
|
||||
sessionKey := getOrEstablishSessionKey(srcIP)
|
||||
@@ -173,8 +183,12 @@ func handleIncomingPkt(pkt []byte) {
|
||||
log.Println(err)
|
||||
}
|
||||
case shared.RESP_GET_PUBKEY:
|
||||
respIP := net.IP(pkt[4:20]).String()
|
||||
pubKey, err := hpke.MLKEM768X25519().NewPublicKey(pkt[20:])
|
||||
if !verifyPacket(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)
|
||||
}
|
||||
@@ -307,7 +321,7 @@ func relayPackets() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
send(buildAuthPkt(shared.ENC_PKT, net.ParseIP(destIP), internalIP, encryptedPkt))
|
||||
send(buildAuthPkt(shared.UNICAST_PKT, net.ParseIP(destIP), internalIP, encryptedPkt))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -324,3 +338,16 @@ func send(req []byte) {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func verifyPacket(pkt []byte) bool {
|
||||
pubKeyBytes, err := hex.DecodeString(SERVER_PUBKEY)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var pubKey mldsa44.PublicKey
|
||||
if err := pubKey.UnmarshalBinary(pubKeyBytes); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return mldsa44.Verify(&pubKey, pkt[2424:], nil, pkt[4:2424])
|
||||
}
|
||||
|
||||
1
go.mod
1
go.mod
@@ -3,6 +3,7 @@ module baalvpn
|
||||
go 1.26.3
|
||||
|
||||
require (
|
||||
github.com/cloudflare/circl v1.6.4
|
||||
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
|
||||
golang.org/x/crypto v0.54.0
|
||||
)
|
||||
|
||||
2
go.sum
2
go.sum
@@ -1,3 +1,5 @@
|
||||
github.com/cloudflare/circl v1.6.4 h1:pOXuDTCEYyzydgUpQ0CQz3LsinKjiSk6nNP5Lt5K64U=
|
||||
github.com/cloudflare/circl v1.6.4/go.mod h1:YxarevkLlbaHuWsxG6vmYNWBEsSp4pnp7j+4VljMavY=
|
||||
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=
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -4,44 +4,53 @@ import "encoding/binary"
|
||||
|
||||
const PROTO_VERSION uint16 = 1
|
||||
|
||||
// pubKey - public key generated by the client
|
||||
// privKey - private key generated by the client
|
||||
// sessionKey - key established between two peers
|
||||
// multicastKey - key shared between everyone for encrypting multicast pkts
|
||||
// authSecret - secret given during registration for authenticating subsequent requests
|
||||
// pubKey - public key generated by the client
|
||||
// privKey - private key generated by the client
|
||||
// serverPubKey - pre-shared public key used for authenticating the server
|
||||
// serverPrivKey - long-term private key used for authenticating the server
|
||||
// sessionKey - per-peer symmetrical key derived via HPKE for encrypting unicast packets
|
||||
// multicastKey - key shared between everyone for encrypting multicast packets
|
||||
// authSecret - secret assigned to a client for authenticating subsequent requests
|
||||
|
||||
// SYM = XChaCha20‑Poly1305 with nonce prepended
|
||||
// HMAC = HMAC-SHA-256
|
||||
// hpke = HPKE(MLKEM768X25519, ChaCha20Poly1305, HKDF-SHA-256)
|
||||
// ds = ML-DSA
|
||||
|
||||
// packet format
|
||||
// [ version - 2 bytes ] [ type - 2 bytes ] [ payload - see below ]
|
||||
const (
|
||||
_ uint16 = iota
|
||||
// (client -> server) requests a challenge to prove the ownership of the privKey
|
||||
// [ pubKey - 1216 bytes ]
|
||||
REQ_GET_CHALLENGE
|
||||
// (server -> client) provides a pubKey-encrypted authSecret
|
||||
// [ ciphertext - 1168 bytes ]
|
||||
// (server -> client) provides an encrypted authSecret
|
||||
// [ ds.Sign(serverPrivKey, rest) - 2420 bytes ] [ HPKE(pubKey, authSecret) - 1168 bytes ]
|
||||
RESP_GET_CHALLENGE
|
||||
// (client -> server) requests an IP
|
||||
// [ HMAC(authSecret, pubKey) - 32 bytes ] [ pubKey - 1216 bytes ]
|
||||
REQ_REGISTER
|
||||
// (server -> client) returns the assigned IP and pubKey-encrypted multicastKey
|
||||
// [ ip - 16 bytes ] [ ciphertext - 1168 bytes ]
|
||||
// (server -> client) returns the assigned IP and encrypted multicastKey
|
||||
// [ ds.Sign(serverPrivKey, rest) - 2420 bytes ] [ ip - 16 bytes ] [ hpke.Seal(pubKey, multicastKey) - 1168 bytes ]
|
||||
RESP_REGISTER
|
||||
// (client -> server -> client2) relays an encrypted packet to a specified peer
|
||||
// [ HMAC(authSecret, rest) - 32 bytes ] [ destIP - 16 bytes ] [ srcIP - 16 bytes ] [ encryptedPkt ]
|
||||
ENC_PKT
|
||||
// (client -> server -> *) broadcasts an encrypted packet
|
||||
// [ HMAC(authSecret, rest) - 32 bytes ] [ encryptedPkt ]
|
||||
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 ]
|
||||
// [ ds.Sign(serverPrivKey, rest) - 2420 bytes ] [ ip - 16 bytes ] [ pubKey - 1216 bytes ]
|
||||
RESP_GET_PUBKEY
|
||||
// (client -> server -> client2) establishes a sessionKey with another peer
|
||||
// [ HMAC(authSecret, rest) - 32 bytes ] [ destIP - 16 bytes ] [ srcIP - 16 bytes ] [ ciphertext - 1120 bytes ]
|
||||
// [ HMAC(authSecret, rest) - 32 bytes ] [ destIP - 16 bytes ] [ srcIP - 16 bytes ] [ hpke.Encap(pubKey) - 1120 bytes ]
|
||||
REQ_ESTABLISH
|
||||
// (client2 -> server -> client) acknowledges the sessionKey was established
|
||||
// [ HMAC(authSecret, rest) - 32 bytes ] [ destIP - 16 bytes ] [ srcIP - 16 bytes ]
|
||||
RESP_ESTABLISH
|
||||
// (client -> server -> client2) relays an encrypted packet to a specified peer
|
||||
// [ HMAC(authSecret, rest) - 32 bytes ] [ destIP - 16 bytes ] [ srcIP - 16 bytes ] [ SYM(sessionKey, pkt) ]
|
||||
UNICAST_PKT
|
||||
// (client -> server -> *) broadcasts an encrypted packet
|
||||
// [ HMAC(authSecret, rest) - 32 bytes ] [ SYM(multicastKey, pkt) ]
|
||||
BROADCAST_PKT
|
||||
)
|
||||
|
||||
func BuildPkt(pktType uint16, parts ...[]byte) []byte {
|
||||
|
||||
Reference in New Issue
Block a user