Addresses CI fixes

This commit is contained in:
riccardom
2026-08-07 17:03:32 +02:00
parent 9610ac5391
commit f6e5e17c5f
9 changed files with 131 additions and 59 deletions

View File

@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"math"
"math/rand"
"net"
"net/netip"
@@ -2893,6 +2894,13 @@ func convertToOfferAnswer(msg *sProto.Message) (*peer.OfferAnswer, error) {
relayIP := decodeRelayIP(msg.GetBody().GetRelayServerIP())
// Ports are uint16 internally; the proto widens them to uint32, so validate the
// range before narrowing (a value that does not fit is a malformed message).
mlkemPort := msg.GetBody().GetMlkemPort()
if mlkemPort > math.MaxUint16 {
return nil, fmt.Errorf("invalid ML-KEM port %d in signalling message", mlkemPort)
}
offerAnswer := peer.OfferAnswer{
IceCredentials: peer.IceCredentials{
UFrag: remoteCred.UFrag,
@@ -2903,7 +2911,7 @@ func convertToOfferAnswer(msg *sProto.Message) (*peer.OfferAnswer, error) {
RosenpassPubKey: rosenpassPubKey,
RosenpassAddr: rosenpassAddr,
MlkemPayload: msg.GetBody().GetMlkemPayload(),
MlkemPort: int(msg.GetBody().GetMlkemPort()),
MlkemPort: uint16(mlkemPort),
RelaySrvAddress: msg.GetBody().GetRelayServerAddress(),
RelaySrvIP: relayIP,
SessionID: sessionID,

View File

@@ -83,14 +83,14 @@ type RosenpassConfig struct {
type PQHandshaker interface {
// OfferPayload returns the KEM offer to embed in an outgoing offer (nil if this
// peer is not the KEM initiator) and the local PQ data-path port to announce.
OfferPayload(remoteKey string) (payload []byte, port int)
OfferPayload(remoteKey string) (payload []byte, port uint16)
// ShouldSendBootstrapOffer reports whether, as the controller, we should reply to a
// received responder offer with our own KEM offer (true only when no exchange is
// already in flight — so we kick the KEM once and ignore further offers).
ShouldSendBootstrapOffer(remoteKey string) bool
// AnswerPayload processes a received KEM offer (nil if absent) and returns the KEM
// answer to embed in the outgoing answer (nil if none) and the local PQ port.
AnswerPayload(remoteKey string, recvOffer []byte) (payload []byte, port int)
AnswerPayload(remoteKey string, recvOffer []byte) (payload []byte, port uint16)
// OnAnswer feeds a received KEM answer (nil if absent).
OnAnswer(remoteKey string, recvAnswer []byte)
// PSK returns the peer's latest derived post-quantum PSK to program at WG
@@ -463,6 +463,9 @@ func (conn *Conn) ConnID() id.ConnID {
// configureConnection starts proxying traffic from/to local Wireguard and sets connection status to StatusConnected
func (conn *Conn) onICEConnectionIsReady(priority conntype.ConnPriority, iceConnInfo ICEConnInfo) {
// Read the PQ PSK before conn.mu to keep the lock order conn.mu -> manager.
pqPSK, pqOK := conn.pqPSK()
conn.mu.Lock()
defer conn.mu.Unlock()
@@ -480,7 +483,7 @@ func (conn *Conn) onICEConnectionIsReady(priority conntype.ConnPriority, iceConn
if conn.currentConnPriority > priority {
conn.Log.Infof("current connection priority (%s) is higher than the new one (%s), do not upgrade connection", conn.currentConnPriority, priority)
conn.statusICE.SetConnected()
conn.updateIceState(iceConnInfo, time.Now())
conn.updateIceState(iceConnInfo, pqOK, time.Now())
return
}
@@ -523,7 +526,7 @@ func (conn *Conn) onICEConnectionIsReady(priority conntype.ConnPriority, iceConn
updateTime := time.Now()
conn.enableWgWatcherIfNeeded(updateTime)
presharedKey := conn.presharedKey(iceConnInfo.RosenpassPubKey)
presharedKey := conn.presharedKey(iceConnInfo.RosenpassPubKey, pqPSK)
if err = conn.endpointUpdater.ConfigureWGEndpoint(ep, presharedKey); err != nil {
conn.handleConfigurationFailure(err, wgProxy)
return
@@ -539,11 +542,14 @@ func (conn *Conn) onICEConnectionIsReady(priority conntype.ConnPriority, iceConn
conn.currentConnPriority = priority
conn.statusICE.SetConnected()
conn.updateIceState(iceConnInfo, updateTime)
conn.updateIceState(iceConnInfo, pqOK, updateTime)
conn.doOnConnected(iceConnInfo.RosenpassPubKey, iceConnInfo.RosenpassAddr, updateTime)
}
func (conn *Conn) onICEStateDisconnected(sessionChanged bool) {
// Read the PQ PSK before conn.mu to keep the lock order conn.mu -> manager.
pqPSK, _ := conn.pqPSK()
conn.mu.Lock()
defer conn.mu.Unlock()
@@ -570,7 +576,7 @@ func (conn *Conn) onICEStateDisconnected(sessionChanged bool) {
// todo consider to move after the ConfigureWGEndpoint
conn.wgProxyRelay.Work()
presharedKey := conn.presharedKey(conn.rosenpassRemoteKey)
presharedKey := conn.presharedKey(conn.rosenpassRemoteKey, pqPSK)
if err := conn.endpointUpdater.SwitchWGEndpoint(conn.wgProxyRelay.EndpointAddr(), presharedKey); err != nil {
conn.Log.Errorf("failed to switch to relay conn: %v", err)
}
@@ -608,6 +614,9 @@ func (conn *Conn) onICEStateDisconnected(sessionChanged bool) {
}
func (conn *Conn) onRelayConnectionIsReady(rci RelayConnInfo) {
// Read the PQ PSK before conn.mu to keep the lock order conn.mu -> manager.
pqPSK, pqOK := conn.pqPSK()
conn.mu.Lock()
defer conn.mu.Unlock()
@@ -636,7 +645,7 @@ func (conn *Conn) onRelayConnectionIsReady(rci RelayConnInfo) {
conn.Log.Debugf("do not switch to relay because current priority is: %s", conn.currentConnPriority.String())
conn.setRelayedProxy(wgProxy)
conn.statusRelay.SetConnected()
conn.updateRelayStatus(rci.relayedConn.RemoteAddr().String(), rci.rosenpassPubKey, time.Now())
conn.updateRelayStatus(rci.relayedConn.RemoteAddr().String(), rci.rosenpassPubKey, pqOK, time.Now())
return
}
@@ -647,7 +656,7 @@ func (conn *Conn) onRelayConnectionIsReady(rci RelayConnInfo) {
}
updateTime := time.Now()
conn.enableWgWatcherIfNeeded(updateTime)
if err := conn.endpointUpdater.ConfigureWGEndpoint(wgProxy.EndpointAddr(), conn.presharedKey(rci.rosenpassPubKey)); err != nil {
if err := conn.endpointUpdater.ConfigureWGEndpoint(wgProxy.EndpointAddr(), conn.presharedKey(rci.rosenpassPubKey, pqPSK)); err != nil {
if err := wgProxy.CloseConn(); err != nil {
conn.Log.Warnf("Failed to close relay connection: %v", err)
}
@@ -666,7 +675,7 @@ func (conn *Conn) onRelayConnectionIsReady(rci RelayConnInfo) {
conn.currentConnPriority = conntype.Relay
conn.statusRelay.SetConnected()
conn.setRelayedProxy(wgProxy)
conn.updateRelayStatus(rci.relayedConn.RemoteAddr().String(), rci.rosenpassPubKey, updateTime)
conn.updateRelayStatus(rci.relayedConn.RemoteAddr().String(), rci.rosenpassPubKey, pqOK, updateTime)
conn.Log.Infof("start to communicate with peer via relay")
conn.doOnConnected(rci.rosenpassPubKey, rci.rosenpassAddr, updateTime)
}
@@ -746,19 +755,15 @@ func (conn *Conn) RequestReoffer() {
func (conn *Conn) onWGDisconnected(watcherCtx context.Context) {
conn.mu.Lock()
defer conn.mu.Unlock()
// watcherCtx guards against a stale watcher tearing down a connection that already superseded it.
if conn.ctx.Err() != nil || watcherCtx.Err() != nil {
conn.mu.Unlock()
return
}
conn.Log.Warnf("WireGuard handshake timeout detected, closing current connection")
if conn.config.PQ != nil {
conn.config.PQ.OnDataPathDown(conn.config.Key)
}
// Close the active connection based on current priority
switch conn.currentConnPriority {
case conntype.Relay:
@@ -771,6 +776,15 @@ func (conn *Conn) onWGDisconnected(watcherCtx context.Context) {
}
conn.escalateWGTimeoutLocked()
pq := conn.config.PQ
key := conn.config.Key
conn.mu.Unlock()
// Signal the PQ manager outside conn.mu: it may re-enter Conn (reoffer) under
// conn.mu, so calling it while holding the lock would invert the lock order.
if pq != nil {
pq.OnDataPathDown(key)
}
}
// escalateWGTimeoutLocked resets the peer's rosenpass state after repeated
@@ -794,14 +808,14 @@ func (conn *Conn) escalateWGTimeoutLocked() {
conn.onDisconnected(conn.config.WgConfig.RemoteKey)
}
func (conn *Conn) updateRelayStatus(relayServerAddr string, rosenpassPubKey []byte, updateTime time.Time) {
func (conn *Conn) updateRelayStatus(relayServerAddr string, rosenpassPubKey []byte, pqEstablished bool, updateTime time.Time) {
peerState := State{
PubKey: conn.config.Key,
ConnStatusUpdate: updateTime,
ConnStatus: conn.evalStatus(),
Relayed: conn.isRelayed(),
RelayServerAddress: relayServerAddr,
RosenpassEnabled: conn.quantumResistant(rosenpassPubKey),
RosenpassEnabled: conn.quantumResistant(rosenpassPubKey, pqEstablished),
}
err := conn.statusRecorder.UpdatePeerRelayedState(peerState)
@@ -810,7 +824,7 @@ func (conn *Conn) updateRelayStatus(relayServerAddr string, rosenpassPubKey []by
}
}
func (conn *Conn) updateIceState(iceConnInfo ICEConnInfo, updateTime time.Time) {
func (conn *Conn) updateIceState(iceConnInfo ICEConnInfo, pqEstablished bool, updateTime time.Time) {
peerState := State{
PubKey: conn.config.Key,
ConnStatusUpdate: updateTime,
@@ -820,7 +834,7 @@ func (conn *Conn) updateIceState(iceConnInfo ICEConnInfo, updateTime time.Time)
RemoteIceCandidateType: iceConnInfo.RemoteIceCandidateType,
LocalIceCandidateEndpoint: iceConnInfo.LocalIceCandidateEndpoint,
RemoteIceCandidateEndpoint: iceConnInfo.RemoteIceCandidateEndpoint,
RosenpassEnabled: conn.quantumResistant(iceConnInfo.RosenpassPubKey),
RosenpassEnabled: conn.quantumResistant(iceConnInfo.RosenpassPubKey, pqEstablished),
}
err := conn.statusRecorder.UpdatePeerICEState(peerState)
@@ -1093,13 +1107,31 @@ func (conn *Conn) AgentVersionString() string {
return conn.config.AgentVersion
}
func (conn *Conn) presharedKey(remoteRosenpassKey []byte) *wgtypes.Key {
// pqPSK returns the post-quantum PSK derived for this peer, if the ML-KEM exchange has
// produced one. It reads the manager WITHOUT conn.mu, so callers fetch it before taking
// conn.mu: the lock order is always conn.mu -> manager, never the reverse (the manager's
// reoffer callback re-enters Conn under conn.mu).
func (conn *Conn) pqPSK() (*wgtypes.Key, bool) {
if conn.config.PQ == nil {
return nil, false
}
psk, ok := conn.config.PQ.PSK(conn.config.Key)
if !ok {
return nil, false
}
return &psk, true
}
// presharedKey resolves the WireGuard preshared key for the peer. pqPSK is the
// post-quantum PSK looked up out of band via pqPSK (nil when none is derived yet), passed
// in so the manager lock is never taken under conn.mu.
func (conn *Conn) presharedKey(remoteRosenpassKey []byte, pqPSK *wgtypes.Key) *wgtypes.Key {
// Post-quantum: once the ML-KEM exchange has derived a PSK for this peer, program
// it here so the peer's next WireGuard handshake adopts it. Applied at peer-config
// time (bootstrap / reconnect); steady-state rotation is pushed separately.
if conn.config.PQ != nil {
if psk, ok := conn.config.PQ.PSK(conn.config.Key); ok {
return &psk
if pqPSK != nil {
return pqPSK
}
if conn.config.PQStrict && conn.pqStrictSentinelKey != nil {
// Fail closed: program a non-matching sentinel so no session forms on a
@@ -1153,16 +1185,8 @@ func isRosenpassEnabled(remoteRosenpassPubKey []byte) bool {
// quantumResistant reports whether the peer's tunnel is post-quantum protected, for
// the status "Quantum resistance" field: either Rosenpass (the remote advertised a
// Rosenpass key) or the ML-KEM exchange (a PQ PSK has been derived for this peer).
func (conn *Conn) quantumResistant(remoteRosenpassPubKey []byte) bool {
if isRosenpassEnabled(remoteRosenpassPubKey) {
return true
}
if conn.config.PQ != nil {
if _, ok := conn.config.PQ.PSK(conn.config.Key); ok {
return true
}
}
return false
func (conn *Conn) quantumResistant(remoteRosenpassPubKey []byte, pqEstablished bool) bool {
return isRosenpassEnabled(remoteRosenpassPubKey) || pqEstablished
}
func evalConnStatus(in connStatusInputs) guard.ConnStatus {

View File

@@ -53,10 +53,12 @@ func TestConn_presharedKey_PQ(t *testing.T) {
c.config.PQ = fakePQ{psk: derivedPSK, ok: true}
c.config.PQStrict = strict
if strict {
sentinel, _ := wgtypes.GenerateKey()
sentinel, err := wgtypes.GenerateKey()
require.NoError(t, err)
c.pqStrictSentinelKey = &sentinel
}
got := c.presharedKey(nil)
pqPSK, _ := c.pqPSK()
got := c.presharedKey(nil, pqPSK)
require.NotNil(t, got)
require.Equal(t, derivedPSK, *got, "the derived PQ PSK must win (strict=%v)", strict)
}
@@ -66,7 +68,8 @@ func TestConn_presharedKey_PQ(t *testing.T) {
c := newConn()
c.config.PQ = fakePQ{ok: false}
c.config.PQStrict = false
got := c.presharedKey(nil)
pqPSK, _ := c.pqPSK()
got := c.presharedKey(nil, pqPSK)
require.NotNil(t, got, "non-strict must not block")
require.Equal(t, nbPSK, *got, "non-strict falls through to the NetBird PSK, not a sentinel")
})
@@ -78,7 +81,8 @@ func TestConn_presharedKey_PQ(t *testing.T) {
c.config.PQ = fakePQ{ok: false}
c.config.PQStrict = true
c.pqStrictSentinelKey = &sentinel
got := c.presharedKey(nil)
pqPSK, _ := c.pqPSK()
got := c.presharedKey(nil, pqPSK)
require.NotNil(t, got)
require.Equal(t, sentinel, *got, "strict must return the blocking sentinel")
require.NotEqual(t, nbPSK, *got, "the sentinel must not be the ordinary key")

View File

@@ -255,8 +255,8 @@ func TestConn_presharedKey(t *testing.T) {
}
conn2.config.RosenpassConfig.PermissiveMode = test.conn2Permissive
conn1PresharedKey := conn1.presharedKey(conn2.config.RosenpassConfig.PubKey)
conn2PresharedKey := conn2.presharedKey(conn1.config.RosenpassConfig.PubKey)
conn1PresharedKey := conn1.presharedKey(conn2.config.RosenpassConfig.PubKey, nil)
conn2PresharedKey := conn2.presharedKey(conn1.config.RosenpassConfig.PubKey, nil)
if test.conn1ExpectedInitialKey {
if conn1PresharedKey == nil {
@@ -294,14 +294,14 @@ func TestConn_presharedKey_RosenpassManaged(t *testing.T) {
// When Rosenpass has already initialized the PSK for this peer,
// presharedKey must return nil to avoid UpdatePeer overwriting it.
conn.rosenpassInitializedPresharedKeyValidator = func(peerKey string) bool { return true }
if k := conn.presharedKey([]byte("remote")); k != nil {
if k := conn.presharedKey([]byte("remote"), nil); k != nil {
t.Fatalf("expected nil presharedKey when Rosenpass manages PSK, got %v", k)
}
// When Rosenpass hasn't taken over yet, presharedKey should provide
// a non-nil initial key (deterministic or from NetBird PSK).
conn.rosenpassInitializedPresharedKeyValidator = func(peerKey string) bool { return false }
if k := conn.presharedKey([]byte("remote")); k == nil {
if k := conn.presharedKey([]byte("remote"), nil); k == nil {
t.Fatalf("expected non-nil presharedKey before Rosenpass manages PSK")
}
}

View File

@@ -47,7 +47,7 @@ type OfferAnswer struct {
// MlkemPort is the peer's ML-KEM PQ service UDP port (bound on its WG overlay
// IP) where data-path rekey messages are sent. Zero when not running the exchange.
MlkemPort int
MlkemPort uint16
// relay server address
RelaySrvAddress string
@@ -222,14 +222,38 @@ func (h *Handshaker) pqControllerReoffer() bool {
// pqRegisterEndpoint feeds the post-quantum handshaker the peer's data-path endpoint
// (its WG overlay IP plus the advertised pq UDP port) learned from a remote offer/answer.
func (h *Handshaker) pqRegisterEndpoint(remotePort int) {
if h.config.PQ == nil || remotePort < 0 || remotePort > 65535 || len(h.config.WgConfig.AllowedIps) == 0 {
func (h *Handshaker) pqRegisterEndpoint(remotePort uint16) {
if h.config.PQ == nil {
return
}
overlay, ok := h.pqPeerOverlayAddr()
if !ok {
return
}
// remotePort may be 0 (the peer omitted it, meaning the default port); the adapter
// resolves 0 to DefaultPort.
addr := netip.AddrPortFrom(h.config.WgConfig.AllowedIps[0].Addr(), uint16(remotePort))
h.config.PQ.SetRemoteAddr(h.config.Key, addr)
h.config.PQ.SetRemoteAddr(h.config.Key, netip.AddrPortFrom(overlay, remotePort))
}
// pqPeerOverlayAddr picks the peer's overlay address for the pq data path. The pq
// transport binds on the local WG overlay IPv4, so an IPv4 AllowedIP is preferred; a v6
// prefix is used only when this interface actually has a v6 overlay, and never in place
// of a usable v4. Returns false when no suitable address exists.
func (h *Handshaker) pqPeerOverlayAddr() (netip.Addr, bool) {
var v6 netip.Addr
for _, p := range h.config.WgConfig.AllowedIps {
a := p.Addr().Unmap()
if a.Is4() {
return a, true
}
if a.Is6() && !v6.IsValid() {
v6 = a
}
}
if v6.IsValid() && h.config.WgConfig.WgInterface.Address().HasIPv6() {
return v6, true
}
return netip.Addr{}, false
}
func (h *Handshaker) SendOffer() error {

View File

@@ -64,7 +64,7 @@ func (s *Signaler) signalOfferAnswer(offerAnswer OfferAnswer, remoteKey string,
RosenpassPubKey: offerAnswer.RosenpassPubKey,
RosenpassAddr: offerAnswer.RosenpassAddr,
MlkemPayload: offerAnswer.MlkemPayload,
MlkemPort: offerAnswer.MlkemPort,
MlkemPort: int(offerAnswer.MlkemPort),
RelaySrvAddress: offerAnswer.RelaySrvAddress,
RelaySrvIP: offerAnswer.RelaySrvIP,
SessionID: sessionIDBytes,

View File

@@ -3,6 +3,7 @@ package pqkem
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -19,8 +20,8 @@ func TestManager_NonCapablePeerNotOffered(t *testing.T) {
offer, err := d.SignalOffer("aaaa")
require.NoError(t, err)
require.Nil(t, offer, "a non-capable peer must not be offered a KEM exchange")
require.Empty(t, wg.failed, "a non-capable peer must not raise a rekey failure")
assert.Nil(t, offer, "a non-capable peer must not be offered a KEM exchange")
assert.Empty(t, wg.failed, "a non-capable peer must not raise a rekey failure")
}
// TestManager_MarkNonCapableCancelsInFlight: if we start an exchange with a peer whose
@@ -42,8 +43,8 @@ func TestManager_MarkNonCapableCancelsInFlight(t *testing.T) {
next, err := d.SignalOffer("aaaa")
require.NoError(t, err)
require.Nil(t, next, "after learning non-capability the peer is no longer offered")
require.Empty(t, wg.failed, "cancelling an in-flight exchange must not raise a failure")
assert.Nil(t, next, "after learning non-capability the peer is no longer offered")
assert.Empty(t, wg.failed, "cancelling an in-flight exchange must not raise a failure")
}
// TestManager_EstablishedPeerNotDowngraded: a stray zero-port observation must not tear
@@ -60,6 +61,6 @@ func TestManager_EstablishedPeerNotDowngraded(t *testing.T) {
// The peer keeps its derived PSK (MarkNonCapable is a no-op once established).
psk, ok := dB.PSK("aaaa")
require.True(t, ok, "an established peer must keep its PSK despite a stray zero")
require.NotEqual(t, PSK{}, psk)
assert.True(t, ok, "an established peer must keep its PSK despite a stray zero")
assert.NotEqual(t, PSK{}, psk)
}

View File

@@ -5,6 +5,7 @@ import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -36,7 +37,7 @@ func TestManager_InitialTimeoutFailsImmediately(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, offer)
require.Eventually(t, func() bool { return failedCount(wg) == 1 }, time.Second, 5*time.Millisecond)
assert.Eventually(t, func() bool { return failedCount(wg) == 1 }, time.Second, 5*time.Millisecond)
}
func TestManager_RekeyToleratesKFailures(t *testing.T) {
@@ -65,10 +66,10 @@ func TestManager_RekeyToleratesKFailures(t *testing.T) {
require.NoError(t, err)
time.Sleep(50 * time.Millisecond)
}
require.Equal(t, 0, failedCount(wgB), "no failure before K attempts")
assert.Equal(t, 0, failedCount(wgB), "no failure before K attempts")
// The K-th failure raises it once.
_, err := dB.startExchangeTest("aaaa", false, ExchangeID{})
require.NoError(t, err)
require.Eventually(t, func() bool { return failedCount(wgB) == 1 }, time.Second, 5*time.Millisecond)
assert.Eventually(t, func() bool { return failedCount(wgB) == 1 }, time.Second, 5*time.Millisecond)
}

View File

@@ -57,14 +57,17 @@ type pqHandshaker struct {
// announcedPort is the PQ data-path port to advertise to peers. It is omitted (0) when
// the manager is on DefaultPort, since peers assume the default when no port is sent;
// only a non-default (collision-forced) port is announced explicitly.
func (p pqHandshaker) announcedPort() int {
func (p pqHandshaker) announcedPort() uint16 {
if port := p.mgr.LocalPort(); port != DefaultPort {
return port
return uint16(port)
}
return 0
}
func (p pqHandshaker) OfferPayload(remoteKey string) ([]byte, int) {
// OfferPayload builds the KEM offer to attach to an outgoing signalling offer for the
// peer, plus the data-path port to announce (0 when on DefaultPort). Payload is nil when
// this side has no offer to send.
func (p pqHandshaker) OfferPayload(remoteKey string) ([]byte, uint16) {
payload, err := p.mgr.SignalOffer(pqkem.RemoteID(remoteKey))
if err != nil {
log.Warnf("pqkem: build offer for %s: %v", remoteKey, err)
@@ -72,11 +75,16 @@ func (p pqHandshaker) OfferPayload(remoteKey string) ([]byte, int) {
return payload, p.announcedPort()
}
// ShouldSendBootstrapOffer reports whether the controller should reply to the peer's
// KEM-less offer with its own bootstrap offer instead of an answer.
func (p pqHandshaker) ShouldSendBootstrapOffer(remoteKey string) bool {
return p.mgr.ShouldSendBootstrapOffer(pqkem.RemoteID(remoteKey))
}
func (p pqHandshaker) AnswerPayload(remoteKey string, recvOffer []byte) ([]byte, int) {
// AnswerPayload processes a received KEM offer (nil when absent) and returns the KEM
// answer to attach to the outgoing signalling answer, plus the data-path port to announce
// (0 when on DefaultPort). An empty offer is treated as a capability signal.
func (p pqHandshaker) AnswerPayload(remoteKey string, recvOffer []byte) ([]byte, uint16) {
if len(recvOffer) == 0 {
// Capability signal (responder side): the KEM offer flows initiator->responder,
// so if we are the responder for this peer (it is the KEM initiator by role) an
@@ -95,6 +103,8 @@ func (p pqHandshaker) AnswerPayload(remoteKey string, recvOffer []byte) ([]byte,
return payload, p.announcedPort()
}
// OnAnswer feeds a received KEM answer (nil when absent) into the exchange. An empty
// answer to our offer is treated as a capability signal on the initiator side.
func (p pqHandshaker) OnAnswer(remoteKey string, recvAnswer []byte) {
if len(recvAnswer) == 0 {
// Capability signal (initiator side): the KEM answer flows responder->initiator,