diff --git a/client/internal/engine.go b/client/internal/engine.go index 617892e43..fd1655d40 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -2895,6 +2895,7 @@ func convertToOfferAnswer(msg *sProto.Message) (*peer.OfferAnswer, error) { Version: msg.GetBody().GetNetBirdVersion(), RosenpassPubKey: rosenpassPubKey, RosenpassAddr: rosenpassAddr, + MlkemPayload: msg.GetBody().GetMlkemPayload(), RelaySrvAddress: msg.GetBody().GetRelayServerAddress(), RelaySrvIP: relayIP, SessionID: sessionID, diff --git a/client/internal/peer/handshaker.go b/client/internal/peer/handshaker.go index 56e82e6e3..9e93958dd 100644 --- a/client/internal/peer/handshaker.go +++ b/client/internal/peer/handshaker.go @@ -39,6 +39,12 @@ type OfferAnswer struct { // This value is the local Rosenpass server address when sending the message RosenpassAddr string + // MlkemPayload carries the post-quantum X25519MLKEM768 handshake message + // (pqkem-framed offer on an OFFER, answer on an ANSWER) that seeds the + // WireGuard PSK. Opaque here — the pqkem library frames and parses it. Nil + // when the peer does not run the ML-KEM PQ exchange. + MlkemPayload []byte + // relay server address RelaySrvAddress string // RelaySrvIP is the IP the remote peer is connected to on its diff --git a/client/internal/peer/signaler.go b/client/internal/peer/signaler.go index 5e437d96b..8e513fc88 100644 --- a/client/internal/peer/signaler.go +++ b/client/internal/peer/signaler.go @@ -63,6 +63,7 @@ func (s *Signaler) signalOfferAnswer(offerAnswer OfferAnswer, remoteKey string, }, RosenpassPubKey: offerAnswer.RosenpassPubKey, RosenpassAddr: offerAnswer.RosenpassAddr, + MlkemPayload: offerAnswer.MlkemPayload, RelaySrvAddress: offerAnswer.RelaySrvAddress, RelaySrvIP: offerAnswer.RelaySrvIP, SessionID: sessionIDBytes, diff --git a/shared/signal/client/client.go b/shared/signal/client/client.go index fb77cb90f..191e92b16 100644 --- a/shared/signal/client/client.go +++ b/shared/signal/client/client.go @@ -52,6 +52,9 @@ type CredentialPayload struct { Credential *Credential RosenpassPubKey []byte RosenpassAddr string + // MlkemPayload is the opaque post-quantum KEM handshake message riding this + // OFFER/ANSWER (see Body.mlkemPayload). Nil when not running the PQ exchange. + MlkemPayload []byte RelaySrvAddress string RelaySrvIP netip.Addr SessionID []byte @@ -89,6 +92,9 @@ func MarshalCredential(myKey wgtypes.Key, remoteKey string, p CredentialPayload) if p.RelaySrvIP.IsValid() { body.RelayServerIP = p.RelaySrvIP.Unmap().AsSlice() } + if len(p.MlkemPayload) > 0 { + body.MlkemPayload = p.MlkemPayload + } return &proto.Message{ Key: myKey.PublicKey().String(), RemoteKey: remoteKey,