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
|
||||
|
||||
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,19 +88,26 @@ func getOrEstablishSessionKey(ip string) []byte {
|
||||
return key
|
||||
}
|
||||
|
||||
log.Println("requesting pubkey of " + ip)
|
||||
// TODO: this definitely shouldnt block the main thread
|
||||
for {
|
||||
// request pubkey every 50ms
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
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)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
for {
|
||||
// TODO: eww
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
if peerPubKey, ok := peerPubKeys.GetOK(ip); ok {
|
||||
log.Println("got pubkey of " + ip)
|
||||
|
||||
ciphertext, sharedSecret, err := xwing.Encapsulate(peerPubKey)
|
||||
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)
|
||||
}
|
||||
@@ -120,10 +127,8 @@ func getOrEstablishSessionKey(ip string) []byte {
|
||||
}
|
||||
}
|
||||
|
||||
sessionKey := sha256.Sum256(sharedSecret)
|
||||
peerSessionKeys.Set(ip, sessionKey[:])
|
||||
return sessionKey[:]
|
||||
}
|
||||
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
1
go.mod
@@ -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
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/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
|
||||
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// TODO: somehow persist IPs
|
||||
// TODO: handle multiple peers behind one NAT
|
||||
// TODO: key rotation
|
||||
// TODO: sha256 -> HKDF
|
||||
package main
|
||||
|
||||
import (
|
||||
|
||||
Reference in New Issue
Block a user