diff --git a/device/noise-protocol.go b/device/noise-protocol.go index 555ce915a..8b4408507 100644 --- a/device/noise-protocol.go +++ b/device/noise-protocol.go @@ -12,6 +12,7 @@ import ( "sync" "time" + "filippo.io/mlkem768" "golang.org/x/crypto/blake2s" "golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/poly1305" @@ -61,8 +62,8 @@ const ( ) const ( - MessageInitiationSize = 148 // size of handshake initiation message - MessageResponseSize = 92 // size of response message + MessageInitiationSize = 1332 // size of handshake initiation message + MessageResponseSize = 1180 // size of response message MessageCookieReplySize = 64 // size of cookie reply message MessageTransportHeaderSize = 16 // size of data preceding content in transport message MessageEncapsulatingTransportSize = 8 // size of optional, free (for use by conn.Bind.Send()) space preceding the transport header @@ -84,23 +85,25 @@ const ( */ type MessageInitiation struct { - Type uint32 - Sender uint32 - Ephemeral NoisePublicKey - Static [NoisePublicKeySize + poly1305.TagSize]byte - Timestamp [tai64n.TimestampSize + poly1305.TagSize]byte - MAC1 [blake2s.Size128]byte - MAC2 [blake2s.Size128]byte + Type uint32 + Sender uint32 + Ephemeral NoisePublicKey + EphemeralPQ [mlkem768.EncapsulationKeySize]byte + Static [NoisePublicKeySize + poly1305.TagSize]byte + Timestamp [tai64n.TimestampSize + poly1305.TagSize]byte + MAC1 [blake2s.Size128]byte + MAC2 [blake2s.Size128]byte } type MessageResponse struct { - Type uint32 - Sender uint32 - Receiver uint32 - Ephemeral NoisePublicKey - Empty [poly1305.TagSize]byte - MAC1 [blake2s.Size128]byte - MAC2 [blake2s.Size128]byte + Type uint32 + Sender uint32 + Receiver uint32 + Ephemeral NoisePublicKey + CiphertextPQ [mlkem768.CiphertextSize]byte + Empty [poly1305.TagSize]byte + MAC1 [blake2s.Size128]byte + MAC2 [blake2s.Size128]byte } type MessageTransport struct { @@ -212,14 +215,16 @@ func (msg *MessageCookieReply) marshal(b []byte) error { type Handshake struct { state handshakeState mutex sync.RWMutex - hash [blake2s.Size]byte // hash value - chainKey [blake2s.Size]byte // chain key - presharedKey NoisePresharedKey // psk - localEphemeral NoisePrivateKey // ephemeral secret key - localIndex uint32 // used to clear hash-table - remoteIndex uint32 // index for sending - remoteStatic NoisePublicKey // long term key, never changes, can be accessed without mutex - remoteEphemeral NoisePublicKey // ephemeral public key + hash [blake2s.Size]byte // hash value + chainKey [blake2s.Size]byte // chain key + presharedKey NoisePresharedKey // psk + localEphemeral NoisePrivateKey // ephemeral secret key + localEphemeralPQ [mlkem768.SeedSize]byte // ephemeral secret PQC key + localIndex uint32 // used to clear hash-table + remoteIndex uint32 // index for sending + remoteStatic NoisePublicKey // long term key, never changes, can be accessed without mutex + remoteEphemeral NoisePublicKey // ephemeral public key + remoteEphemeralPQ [mlkem768.EncapsulationKeySize]byte precomputedStaticStatic [NoisePublicKeySize]byte // precomputed shared secret lastTimestamp tai64n.Timestamp lastInitiationConsumption time.Time @@ -287,9 +292,16 @@ func (device *Device) CreateMessageInitiation(peer *Peer) (*MessageInitiation, e handshake.mixHash(handshake.remoteStatic[:]) + dk, err := mlkem768.GenerateKey() + if err != nil { + return nil, err + } + handshake.localEphemeralPQ = [64]byte(dk.Bytes()) + msg := MessageInitiation{ - Type: MessageInitiationType, - Ephemeral: handshake.localEphemeral.publicKey(), + Type: MessageInitiationType, + Ephemeral: handshake.localEphemeral.publicKey(), + EphemeralPQ: [mlkem768.EncapsulationKeySize]byte(dk.EncapsulationKey()), } handshake.mixKey(msg.Ephemeral[:]) @@ -425,6 +437,8 @@ func (device *Device) ConsumeMessageInitiation(msg *MessageInitiation) *Peer { handshake.chainKey = chainKey handshake.remoteIndex = msg.Sender handshake.remoteEphemeral = msg.Ephemeral + handshake.remoteEphemeralPQ = msg.EphemeralPQ + if timestamp.After(handshake.lastTimestamp) { handshake.lastTimestamp = timestamp } @@ -486,6 +500,16 @@ func (device *Device) CreateMessageResponse(peer *Peer) (*MessageResponse, error } handshake.mixKey(ss[:]) + // set preshared key + + ciphertext, sspq, err := mlkem768.Encapsulate(handshake.remoteEphemeralPQ[:]) + if err != nil { + return nil, err + } + msg.CiphertextPQ = [mlkem768.CiphertextSize]byte(ciphertext) + + handshake.presharedKey = NoisePresharedKey(sspq) + // add preshared key var tau [blake2s.Size]byte @@ -562,6 +586,21 @@ func (device *Device) ConsumeMessageResponse(msg *MessageResponse) *Peer { mixKey(&chainKey, &chainKey, ss[:]) setZero(ss[:]) + // set preshared key + + dk, err := mlkem768.NewKeyFromSeed(handshake.localEphemeralPQ[:]) + if err != nil { + return false + } + sspq, err := mlkem768.Decapsulate(dk, msg.CiphertextPQ[:]) + if err != nil { + return false + } + handshake.presharedKey = NoisePresharedKey(sspq) + + setZero(sspq[:]) + setZero(handshake.localEphemeralPQ[:]) + // add preshared key (psk) var tau [blake2s.Size]byte diff --git a/go.mod b/go.mod index 9c9b02a66..de1ebf994 100644 --- a/go.mod +++ b/go.mod @@ -1,16 +1,19 @@ module github.com/tailscale/wireguard-go -go 1.20 +go 1.21.3 + +toolchain go1.24.3 require ( - golang.org/x/crypto v0.13.0 - golang.org/x/net v0.15.0 - golang.org/x/sys v0.12.0 + golang.org/x/crypto v0.26.0 + golang.org/x/net v0.21.0 + golang.org/x/sys v0.24.0 golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 ) require ( + filippo.io/mlkem768 v0.0.0-20241021091500-d85de16e2039 // indirect github.com/google/btree v1.0.1 // indirect golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect ) diff --git a/go.sum b/go.sum index 6bcecea3f..a560842c9 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,19 @@ +filippo.io/mlkem768 v0.0.0-20241021091500-d85de16e2039 h1:I/alPPIVzEkPeQKVU7Sl5gv/sQ0IC4zgqHiACrSgUW8= +filippo.io/mlkem768 v0.0.0-20241021091500-d85de16e2039/go.mod h1:IkpYfciLz5fI/S4/Z0NlhR4cpv6ubCMDnIwAe0XiojA= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=