Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
f8872a0
tun: use add-with-carry in checksumNoFold()
dinhngtu Jun 20, 2024
a193cf4
tun: darwin: fetch flags and mtu from if_msghdr directly
ruokeqx Jan 2, 2025
7784c5a
global: replaced unused function params with _
tomholford May 4, 2025
8916471
device: use rand.NewSource instead of rand.Seed
tomholford May 4, 2025
e5a7e5f
rwcancel: fix wrong poll event flag on ReadyWrite
win-t Jun 7, 2023
e49aab5
device: reduce RoutineHandshake allocations
AlexanderYastrebov Dec 26, 2024
8f357d8
device: make unmarshall length checks exact
zx2c4 May 15, 2025
ae5a1c6
version: bump snapshot
zx2c4 May 15, 2025
87b78d5
device: add support for removing allowedips individually
zx2c4 May 20, 2025
3d12258
device: optimize message encoding
AlexanderYastrebov May 17, 2025
56a3506
conn: don't enable GRO on Linux < 5.12
zx2c4 May 21, 2025
853ff70
version: bump snapshot
zx2c4 May 21, 2025
4882a13
chore: add indirect dependency for cloudflare/circl v1.6.1
Mateusvff Aug 30, 2025
d506965
feat: add ML-KEM post-quantum key types
Mateusvff Aug 30, 2025
8851ccd
feat: (device) add ML-KEM public key to peer handshake struct
Mateusvff Aug 30, 2025
e66ed2c
feat(device): Add ML-KEM key pair fields to staticIdentity
Mateusvff Aug 30, 2025
9e84b95
feat(device): Update MessageInitiation to include ML-KEM ciphertext size
Mateusvff Aug 30, 2025
a04398a
feat(device): Add post-quantum encapsulation in CreateMessageInitiation
Mateusvff Aug 31, 2025
aadc8a4
feat(device): Implement post-quantum decapsulation in ConsumeMessageI…
Mateusvff Aug 31, 2025
68be555
feat(device): Add function for quantum key generation
Mateusvff Sep 23, 2025
87626c1
feat(device): Enhance UAPI handling for ML-KEM keys
Mateusvff Sep 23, 2025
0f31fc3
feat(device): Update KDF2 calls to include dummy parameter for combin…
Mateusvff Sep 24, 2025
a05eabe
fix old tests and add new ones
Eruel6 Sep 29, 2025
2f51762
feat: add benchmark test file
Eruel6 Sep 30, 2025
92c9c80
fix: add new tests to utlize hybrid handshake
Eruel6 Oct 16, 2025
d80d6d9
feat(device): Add MLDSA key and signature types with appropriate sizes
Oct 13, 2025
26943f4
feat(device): Add MLDSA public key to Handshake struct and include pr…
Oct 13, 2025
cada0b9
feat(device): Update handshake message sizes to include MLDSA signature
Mateusvff Oct 13, 2025
10896e6
feat(device): Update MessageInitiation struct to include Signature in…
Mateusvff Oct 13, 2025
fabb5d7
feat(device): Implement MLDSA signature generation in CreateMessageIn…
Mateusvff Oct 26, 2025
4193bcc
feat(device): Add MLDSA signature verification in ConsumeMessageIniti…
Mateusvff Oct 26, 2025
5a1d36e
feat(device): Add MLDSA key pair generation function
Mateusvff Oct 26, 2025
e4017b2
feat: add tests for MLDSA
Eruel6 Nov 6, 2025
b2e63c1
fix: add MLDSA to benchmark
Eruel6 Nov 15, 2025
4deaf38
Merge branch 'master' into master
Mateusvff Nov 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions device/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,12 @@ type Device struct {

staticIdentity struct {
sync.RWMutex
privateKey NoisePrivateKey
publicKey NoisePublicKey
privateKey NoisePrivateKey
publicKey NoisePublicKey
mlkemPrivateKey MLKEMPrivateKey
mlkemPublicKey MLKEMPublicKey
mldsaPrivateKey MLDSAPrivateKey
mldsaPublicKey MLDSAPublicKey
}

peers struct {
Expand Down
185 changes: 184 additions & 1 deletion device/device_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ import (
"testing"
"time"

"github.com/cloudflare/circl/kem/kyber/kyber1024"
"github.com/cloudflare/circl/sign/dilithium/mode5"


"golang.zx2c4.com/wireguard/conn"
"golang.zx2c4.com/wireguard/conn/bindtest"
"golang.zx2c4.com/wireguard/tun"
Expand Down Expand Up @@ -189,6 +193,10 @@ func genTestPair(tb testing.TB, realSocket bool) (pair testPair) {
// The device is ready. Close it when the test completes.
tb.Cleanup(p.dev.Close)
}

installMLKEMKeys(tb, &pair)
installMLDSAKeys(tb, &pair)

return
}

Expand Down Expand Up @@ -405,7 +413,6 @@ func goroutineLeakCheck(t *testing.T) {
if t.Failed() {
return
}
// Give goroutines time to exit, if they need it.
for i := 0; i < 10000; i++ {
if runtime.NumGoroutine() <= startGoroutines {
return
Expand Down Expand Up @@ -474,3 +481,179 @@ func TestBatchSize(t *testing.T) {
t.Errorf("expected batch size %d, got %d", want, got)
}
}

func installMLKEMKeys(t testing.TB, pair *testPair) {
t.Helper()
scheme := kyber1024.Scheme()
pk0, sk0, err := scheme.GenerateKeyPair()
if err != nil { t.Fatal(err) }
pk0b, _ := pk0.MarshalBinary()
sk0b, _ := sk0.MarshalBinary()
pk1, sk1, err := scheme.GenerateKeyPair()
if err != nil { t.Fatal(err) }
pk1b, _ := pk1.MarshalBinary()
sk1b, _ := sk1.MarshalBinary()

if err := pair[0].dev.IpcSet(uapiCfg("mlkem_private_key", hex.EncodeToString(sk0b))); err != nil { t.Fatal(err) }
if err := pair[1].dev.IpcSet(uapiCfg("mlkem_private_key", hex.EncodeToString(sk1b))); err != nil { t.Fatal(err) }
var pub0, pub1 NoisePublicKey
for k := range pair[0].dev.peers.keyMap { pub0 = k; break }
for k := range pair[1].dev.peers.keyMap { pub1 = k; break }
cfgPeer0 := uapiCfg(
"public_key", hex.EncodeToString(pub0[:]),
"mlkem_public_key", hex.EncodeToString(pk1b),
)
cfgPeer1 := uapiCfg(
"public_key", hex.EncodeToString(pub1[:]),
"mlkem_public_key", hex.EncodeToString(pk0b),
)
if err := pair[0].dev.IpcSet(cfgPeer0); err != nil { t.Fatal(err) }
if err := pair[1].dev.IpcSet(cfgPeer1); err != nil { t.Fatal(err) }
}

func installMLDSAKeys(t testing.TB, pair *testPair) {
t.Helper()

dil := mode5.Scheme()
pk0, sk0, err := dil.GenerateKey()
if err != nil { t.Fatal(err) }
pk1, sk1, err := dil.GenerateKey()
if err != nil { t.Fatal(err) }

pk0b, _ := pk0.MarshalBinary()
sk0b, _ := sk0.MarshalBinary()
pk1b, _ := pk1.MarshalBinary()
sk1b, _ := sk1.MarshalBinary()

copy(pair[0].dev.staticIdentity.mldsaPrivateKey[:], sk0b)
copy(pair[1].dev.staticIdentity.mldsaPrivateKey[:], sk1b)

var peer0, peer1 *Peer
for _, p := range pair[0].dev.peers.keyMap { peer0 = p; break }
for _, p := range pair[1].dev.peers.keyMap { peer1 = p; break }
if peer0 == nil || peer1 == nil {
t.Fatal("não foi possível localizar peers para configurar MLDSA")
}

copy(peer0.handshake.remoteMLDSAStatic[:], pk1b)
copy(peer1.handshake.remoteMLDSAStatic[:], pk0b)
}

func TestMLKEMKeyGeneration(t *testing.T) {
pub, priv, err := GenerateQuantumKeyPair()
if err != nil { t.Fatal(err) }

scheme := kyber1024.Scheme()
if len(pub) != scheme.PublicKeySize() {
t.Fatalf("pub size mismatch: got %d, want %d", len(pub), scheme.PublicKeySize())
}
if len(priv) != scheme.PrivateKeySize() {
t.Fatalf("priv size mismatch: got %d, want %d", len(priv), scheme.PrivateKeySize())
}

if _, err := scheme.UnmarshalBinaryPublicKey(pub); err != nil { t.Fatal(err) }
if _, err := scheme.UnmarshalBinaryPrivateKey(priv); err != nil { t.Fatal(err) }
}


func TestMLKEMEncapDecap(t *testing.T) {
scheme := kyber1024.Scheme()
pk, sk, err := scheme.GenerateKeyPair()
if err != nil { t.Fatal(err) }

ct, ssEnc, err := scheme.Encapsulate(pk)
if err != nil { t.Fatal(err) }
ssDec, err := scheme.Decapsulate(sk, ct)
if err != nil { t.Fatal(err) }

if !bytes.Equal(ssEnc, ssDec) {
t.Fatal("ML-KEM shared secrets differ")
}
}

func TestNoiseHandshakeWithMLKEM(t *testing.T) {
skA, _ := newPrivateKey()
skB, _ := newPrivateKey()

tunA := tuntest.NewChannelTUN()
tunB := tuntest.NewChannelTUN()

devA := NewDevice(tunA.TUN(), conn.NewDefaultBind(), NewLogger(LogLevelError, ""))
devB := NewDevice(tunB.TUN(), conn.NewDefaultBind(), NewLogger(LogLevelError, ""))

defer devA.Close()
defer devB.Close()

if err := devA.SetPrivateKey(skA); err != nil { t.Fatal(err) }
if err := devB.SetPrivateKey(skB); err != nil { t.Fatal(err) }

peerB, err := devA.NewPeer(skB.publicKey())
if err != nil { t.Fatal(err) }
peerA, err := devB.NewPeer(skA.publicKey())
if err != nil { t.Fatal(err) }

scheme := kyber1024.Scheme()
pkA, skAkem, _ := scheme.GenerateKeyPair()
pkB, skBkem, _ := scheme.GenerateKeyPair()
pkAb, _ := pkA.MarshalBinary()
pkBb, _ := pkB.MarshalBinary()
skAb, _ := skAkem.MarshalBinary()
skBb, _ := skBkem.MarshalBinary()

if err := devA.IpcSet(uapiCfg("mlkem_private_key", hex.EncodeToString(skAb))); err != nil { t.Fatal(err) }
if err := devB.IpcSet(uapiCfg("mlkem_private_key", hex.EncodeToString(skBb))); err != nil { t.Fatal(err) }

if err := devA.IpcSet(uapiCfg("public_key", hex.EncodeToString(peerB.handshake.remoteStatic[:]),
"mlkem_public_key", hex.EncodeToString(pkBb))); err != nil { t.Fatal(err) }
if err := devB.IpcSet(uapiCfg("public_key", hex.EncodeToString(peerA.handshake.remoteStatic[:]),
"mlkem_public_key", hex.EncodeToString(pkAb))); err != nil { t.Fatal(err) }

dil := mode5.Scheme()
pkAS, skAS, _ := dil.GenerateKey()
pkBS, skBS, _ := dil.GenerateKey()
pkASb, _ := pkAS.MarshalBinary()
pkBSb, _ := pkBS.MarshalBinary()
skASb, _ := skAS.MarshalBinary()
skBSb, _ := skBS.MarshalBinary()

copy(devA.staticIdentity.mldsaPrivateKey[:], skASb)
copy(devB.staticIdentity.mldsaPrivateKey[:], skBSb)

copy(peerB.handshake.remoteMLDSAStatic[:], pkBSb)
copy(peerA.handshake.remoteMLDSAStatic[:], pkASb)

peerA.Start()
peerB.Start()

msg1, err := devA.CreateMessageInitiation(peerB)
if err != nil { t.Fatal(err) }
if p := devB.ConsumeMessageInitiation(msg1); p == nil {
t.Fatal("handshake failed at initiation (ML-KEM)")
}

msg2, err := devB.CreateMessageResponse(peerA)
if err != nil { t.Fatal(err) }
if p := devA.ConsumeMessageResponse(msg2); p == nil {
t.Fatal("handshake failed at response (ML-KEM)")
}

if err := peerA.BeginSymmetricSession(); err != nil { t.Fatal(err) }
if err := peerB.BeginSymmetricSession(); err != nil { t.Fatal(err) }

keyA := peerA.keypairs.next.Load()
keyB := peerB.keypairs.current

msg := []byte("pqc wireguard ok")
var nonce [12]byte

out := keyA.send.Seal(nil, nonce[:], msg, nil)
plain, err := keyB.receive.Open(nil, nonce[:], out, nil)
if err != nil { t.Fatal(err) }
if !bytes.Equal(plain, msg) { t.Fatal("A->B decrypt mismatch") }

out = keyB.send.Seal(nil, nonce[:], msg, nil)
plain, err = keyA.receive.Open(nil, nonce[:], out, nil)
if err != nil { t.Fatal(err) }
if !bytes.Equal(plain, msg) { t.Fatal("B->A decrypt mismatch") }

}
169 changes: 169 additions & 0 deletions device/mldsa_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package device

import (
"bytes"
"testing"
"encoding/hex"

"github.com/cloudflare/circl/sign/dilithium/mode5"
"github.com/cloudflare/circl/kem/kyber/kyber1024"
)

func TestGenerateMLDSAKeyPair(t *testing.T) {
pub, priv, err := GenerateMLDSAKeyPair()
if err != nil {
t.Fatalf("erro gerando MLDSA: %v", err)
}
if len(pub) != MLDSAPublicKeySize || len(priv) != MLDSAPrivateKeySize {
t.Fatalf("tamanhos inválidos: pub=%d priv=%d", len(pub), len(priv))
}
s := mode5.Scheme()
if _, err := s.UnmarshalBinaryPublicKey(pub); err != nil {
t.Fatalf("publicKey inválida: %v", err)
}
if _, err := s.UnmarshalBinaryPrivateKey(priv); err != nil {
t.Fatalf("privateKey inválida: %v", err)
}
}

func TestMLDSASignVerify(t *testing.T) {
s := mode5.Scheme()
pk, sk, err := s.GenerateKey()
if err != nil {
t.Fatalf("erro gerando par mldsa: %v", err)
}
msg := []byte("wireguard + mldsa test")
sig := s.Sign(sk, msg, nil)

if !s.Verify(pk, msg, sig, nil) {
t.Fatalf("assinatura MLDSA não verificou")
}
if s.Verify(pk, append(msg, 0x01), sig, nil) {
t.Fatalf("assinatura deveria falhar em msg alterada")
}
}

func mustCopy(dst []byte, src []byte) {
if len(dst) != len(src) { panic("tam inválido") }
copy(dst, src)
}

func TestHybridHandshakeWithMLDSASignature(t *testing.T) {
dev1 := randDevice(t)
dev2 := randDevice(t)
defer dev1.Close()
defer dev2.Close()

kyb := kyber1024.Scheme()
pkK1, skK1, _ := kyb.GenerateKeyPair()
pkK2, skK2, _ := kyb.GenerateKeyPair()
pubK1, _ := pkK1.MarshalBinary()
privK1, _ := skK1.MarshalBinary()
pubK2, _ := pkK2.MarshalBinary()
privK2, _ := skK2.MarshalBinary()

mldsa := mode5.Scheme()
pkS1, skS1, _ := mldsa.GenerateKey()
pkS2, skS2, _ := mldsa.GenerateKey()
pubS1, _ := pkS1.MarshalBinary()
privS1, _ := skS1.MarshalBinary()
pubS2, _ := pkS2.MarshalBinary()
privS2, _ := skS2.MarshalBinary()

mustCopy(dev1.staticIdentity.mlkemPrivateKey[:], privK1)
mustCopy(dev2.staticIdentity.mlkemPrivateKey[:], privK2)
mustCopy(dev1.staticIdentity.mldsaPrivateKey[:], privS1)
mustCopy(dev2.staticIdentity.mldsaPrivateKey[:], privS2)

peer1, err := dev2.NewPeer(dev1.staticIdentity.privateKey.publicKey())
if err != nil { t.Fatal(err) }
peer2, err := dev1.NewPeer(dev2.staticIdentity.privateKey.publicKey())
if err != nil { t.Fatal(err) }

mustCopy(peer1.handshake.remoteMLKEMStatic[:], pubK1)
mustCopy(peer2.handshake.remoteMLKEMStatic[:], pubK2)
mustCopy(peer1.handshake.remoteMLDSAStatic[:], pubS1)
mustCopy(peer2.handshake.remoteMLDSAStatic[:], pubS2)

peer1.Start()
peer2.Start()

init, err := dev1.CreateMessageInitiation(peer2)
if err != nil {
t.Fatalf("CreateMessageInitiation falhou: %v", err)
}
if p := dev2.ConsumeMessageInitiation(init); p == nil {
t.Fatalf("ConsumeMessageInitiation falhou (assinatura/MLKEM?)")
}

resp, err := dev2.CreateMessageResponse(peer1)
if err != nil {
t.Fatalf("CreateMessageResponse falhou: %v", err)
}
if p := dev1.ConsumeMessageResponse(resp); p == nil {
t.Fatalf("ConsumeMessageResponse falhou")
}

if err := peer1.BeginSymmetricSession(); err != nil {
t.Fatalf("peer1.BeginSymmetricSession: %v", err)
}
if err := peer2.BeginSymmetricSession(); err != nil {
t.Fatalf("peer2.BeginSymmetricSession: %v", err)
}

key1 := peer1.keypairs.next.Load()
key2 := peer2.keypairs.current
plain := []byte("ok mldsa+mlkem+noise")
var nonce [12]byte
c := key1.send.Seal(nil, nonce[:], plain, nil)
out, err := key2.receive.Open(nil, nonce[:], c, nil)
if err != nil || !bytes.Equal(out, plain) {
t.Fatalf("falha cifrar/decifrar: %v", err)
}
}

func TestHybridHandshake_MLDSAInvalidSignature(t *testing.T) {
dev1 := randDevice(t)
dev2 := randDevice(t)
defer dev1.Close(); defer dev2.Close()

kyb := kyber1024.Scheme()
pkK1, skK1, _ := kyb.GenerateKeyPair()
pkK2, skK2, _ := kyb.GenerateKeyPair()
pubK1, _ := pkK1.MarshalBinary()
privK1, _ := skK1.MarshalBinary()
pubK2, _ := pkK2.MarshalBinary()
privK2, _ := skK2.MarshalBinary()
mustCopy(dev1.staticIdentity.mlkemPrivateKey[:], privK1)
mustCopy(dev2.staticIdentity.mlkemPrivateKey[:], privK2)

s := mode5.Scheme()
pkGood, skGood, _ := s.GenerateKey()
pkWrong, _, _ := s.GenerateKey()
privGood, _ := skGood.MarshalBinary()
pubGood, _ := pkGood.MarshalBinary()
pubWrong, _ := pkWrong.MarshalBinary()
mustCopy(dev1.staticIdentity.mldsaPrivateKey[:], privGood)

peer1, _ := dev2.NewPeer(dev1.staticIdentity.privateKey.publicKey())
peer2, _ := dev1.NewPeer(dev2.staticIdentity.privateKey.publicKey())
peer1.Start(); peer2.Start()
mustCopy(peer1.handshake.remoteMLKEMStatic[:], pubK1)
mustCopy(peer2.handshake.remoteMLKEMStatic[:], pubK2)

mustCopy(peer1.handshake.remoteMLDSAStatic[:], pubWrong)

init, err := dev1.CreateMessageInitiation(peer2)
if err != nil {
t.Fatalf("CreateMessageInitiation falhou: %v", err)
}
if p := dev2.ConsumeMessageInitiation(init); p != nil {
t.Fatalf("assinatura inválida deveria falhar")
}

mustCopy(peer1.handshake.remoteMLDSAStatic[:], pubGood)
if p := dev2.ConsumeMessageInitiation(init); p == nil {
t.Fatalf("deveria aceitar com a pública correta")
}
_ = hex.EncodeToString
}
Loading