use crypto/hpke for MLKEM768X25519

This commit is contained in:
2026-07-22 16:05:30 +02:00
parent a2d647f3af
commit f9f0df478d
6 changed files with 86 additions and 46 deletions

24
LICENSE Normal file
View File

@@ -0,0 +1,24 @@
BSD 2-Clause License
Copyright (c) 2026, Antoni Piasecki
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

9
README.md Normal file
View File

@@ -0,0 +1,9 @@
# baalvpn
A post-quantum 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
## Third-party dependencies
* [`github.com/songgao/water`](https://github.com/songgao/water) - cross-platform wrapper around TUN interfaces

View File

@@ -1,7 +1,7 @@
package main
import (
"crypto/sha256"
"crypto/hpke"
"encoding/binary"
"encoding/hex"
"log"
@@ -13,7 +13,6 @@ import (
"baalvpn/shared"
"filippo.io/mlkem768/xwing"
"github.com/songgao/water"
)
@@ -27,10 +26,10 @@ var (
serverAddr *net.UDPAddr
iface *water.Interface
conn *net.UDPConn
privKey *xwing.DecapsulationKey
privKey hpke.PrivateKey
establishMutex sync.Mutex
peerPubKeys = shared.NewTMap[string, []byte]()
peerPubKeys = shared.NewTMap[string, hpke.PublicKey]()
peerSessionKeys = shared.NewTMap[string, []byte]()
peerEstablishAcks = shared.NewTMap[string, bool]()
)
@@ -63,13 +62,14 @@ func main() {
func register() {
var err error
privKey, err = xwing.GenerateKey()
privKey, err = hpke.MLKEM768X25519().GenerateKey()
if err != nil {
panic(err)
}
pubKey := privKey.EncapsulationKey()
pubKey := privKey.PublicKey()
req := shared.BuildPkt(shared.REQ_REGISTER, pubKey)
req := shared.BuildPkt(shared.REQ_REGISTER, pubKey.Bytes())
if _, err := conn.WriteToUDP(req, serverAddr); err != nil {
panic(err)
}
@@ -88,42 +88,47 @@ func getOrEstablishSessionKey(ip string) []byte {
return key
}
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)
}
// TODO: this definitely shouldnt block the main thread
for {
// TODO: eww
// request pubkey every 50ms
time.Sleep(50 * time.Millisecond)
if peerPubKey, ok := peerPubKeys.GetOK(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)
peerPubKey, ok := peerPubKeys.GetOK(ip)
if !ok {
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 peerEstablishAcks.Get(ip) {
peerEstablishAcks.Delete(ip)
break
}
}
sessionKey := sha256.Sum256(sharedSecret)
peerSessionKeys.Set(ip, sessionKey[:])
return sessionKey[:]
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", 32)
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.Get(ip) {
peerEstablishAcks.Delete(ip)
break
}
}
peerSessionKeys.Set(ip, sessionKey)
return sessionKey
}
}
@@ -193,21 +198,27 @@ func handlePkt(pkt []byte) {
}
case shared.RESP_GET_PUBKEY:
respIP := net.IP(pkt[4:20]).String()
pubKey := pkt[20:]
pubKey, err := hpke.MLKEM768X25519().NewPublicKey(pkt[20:])
if err != nil {
panic(err)
}
peerPubKeys.Set(respIP, pubKey)
case shared.REQ_ESTABLISH:
srcIP := net.IP(pkt[20:36]).String()
ciphertext := pkt[36:]
sharedSecret, err := xwing.Decapsulate(privKey, ciphertext)
r, err := hpke.NewRecipient(ciphertext, privKey, hpke.HKDFSHA256(), hpke.ExportOnly(), nil)
if err != nil {
panic(err)
}
sessionKey, err := r.Export("baalvpn", 32)
if err != nil {
panic(err)
}
sessionKey := sha256.Sum256(sharedSecret)
peerSessionKeys.Set(srcIP, sessionKey[:])
log.Println("received session key from", srcIP)
peerSessionKeys.Set(srcIP, sessionKey)
reqData := append(net.ParseIP(srcIP), internalIP...)
req := shared.BuildPkt(shared.RESP_ESTABLISH, reqData)
if _, err := conn.WriteToUDP(req, serverAddr); err != nil {

1
go.mod
View File

@@ -3,7 +3,6 @@ module baalvpn
go 1.26.3
require (
filippo.io/mlkem768 v0.0.0-20260214141301-2e7bebc7d88d
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
golang.org/x/crypto v0.54.0
)

2
go.sum
View File

@@ -1,5 +1,3 @@
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/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=

View File

@@ -3,7 +3,6 @@
// TODO: somehow persist IPs
// TODO: handle multiple peers behind one NAT
// TODO: key rotation
// TODO: sha256 -> HKDF
package main
import (