use crypto/hpke for MLKEM768X25519
This commit is contained in:
24
LICENSE
Normal file
24
LICENSE
Normal 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
9
README.md
Normal 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
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/hpke"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"log"
|
"log"
|
||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
|
|
||||||
"baalvpn/shared"
|
"baalvpn/shared"
|
||||||
|
|
||||||
"filippo.io/mlkem768/xwing"
|
|
||||||
"github.com/songgao/water"
|
"github.com/songgao/water"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,10 +26,10 @@ var (
|
|||||||
serverAddr *net.UDPAddr
|
serverAddr *net.UDPAddr
|
||||||
iface *water.Interface
|
iface *water.Interface
|
||||||
conn *net.UDPConn
|
conn *net.UDPConn
|
||||||
privKey *xwing.DecapsulationKey
|
privKey hpke.PrivateKey
|
||||||
|
|
||||||
establishMutex sync.Mutex
|
establishMutex sync.Mutex
|
||||||
peerPubKeys = shared.NewTMap[string, []byte]()
|
peerPubKeys = shared.NewTMap[string, hpke.PublicKey]()
|
||||||
peerSessionKeys = shared.NewTMap[string, []byte]()
|
peerSessionKeys = shared.NewTMap[string, []byte]()
|
||||||
peerEstablishAcks = shared.NewTMap[string, bool]()
|
peerEstablishAcks = shared.NewTMap[string, bool]()
|
||||||
)
|
)
|
||||||
@@ -63,13 +62,14 @@ func main() {
|
|||||||
|
|
||||||
func register() {
|
func register() {
|
||||||
var err error
|
var err error
|
||||||
privKey, err = xwing.GenerateKey()
|
|
||||||
|
privKey, err = hpke.MLKEM768X25519().GenerateKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
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 {
|
if _, err := conn.WriteToUDP(req, serverAddr); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -88,42 +88,47 @@ func getOrEstablishSessionKey(ip string) []byte {
|
|||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("requesting pubkey of " + ip)
|
// TODO: this definitely shouldnt block the main thread
|
||||||
req := shared.BuildPkt(shared.REQ_GET_PUBKEY, net.ParseIP(ip))
|
|
||||||
if _, err := conn.WriteToUDP(req, serverAddr); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// TODO: eww
|
// request pubkey every 50ms
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
if peerPubKey, ok := peerPubKeys.GetOK(ip); ok {
|
peerPubKey, ok := peerPubKeys.GetOK(ip)
|
||||||
log.Println("got pubkey of " + ip)
|
if !ok {
|
||||||
|
req := shared.BuildPkt(shared.REQ_GET_PUBKEY, net.ParseIP(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 {
|
if _, err := conn.WriteToUDP(req, serverAddr); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
continue
|
||||||
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[:]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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:
|
case shared.RESP_GET_PUBKEY:
|
||||||
respIP := net.IP(pkt[4:20]).String()
|
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)
|
peerPubKeys.Set(respIP, pubKey)
|
||||||
case shared.REQ_ESTABLISH:
|
case shared.REQ_ESTABLISH:
|
||||||
srcIP := net.IP(pkt[20:36]).String()
|
srcIP := net.IP(pkt[20:36]).String()
|
||||||
ciphertext := pkt[36:]
|
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 {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionKey := sha256.Sum256(sharedSecret)
|
|
||||||
peerSessionKeys.Set(srcIP, sessionKey[:])
|
|
||||||
|
|
||||||
log.Println("received session key from", srcIP)
|
log.Println("received session key from", srcIP)
|
||||||
|
peerSessionKeys.Set(srcIP, sessionKey)
|
||||||
|
|
||||||
reqData := append(net.ParseIP(srcIP), internalIP...)
|
reqData := append(net.ParseIP(srcIP), internalIP...)
|
||||||
req := shared.BuildPkt(shared.RESP_ESTABLISH, reqData)
|
req := shared.BuildPkt(shared.RESP_ESTABLISH, reqData)
|
||||||
if _, err := conn.WriteToUDP(req, serverAddr); err != nil {
|
if _, err := conn.WriteToUDP(req, serverAddr); err != nil {
|
||||||
|
|||||||
1
go.mod
1
go.mod
@@ -3,7 +3,6 @@ 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
|
||||||
)
|
)
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -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 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=
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
// 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: key rotation
|
||||||
// TODO: sha256 -> HKDF
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
Reference in New Issue
Block a user