From 6cef0d8bee5951c6204b031bcbb86572af9b79c8 Mon Sep 17 00:00:00 2001 From: riccardom Date: Thu, 23 Jul 2026 11:03:40 +0200 Subject: [PATCH] Epurate wg refs --- client/internal/pqkem/callbacks.go | 25 ++++--- client/internal/pqkem/convergence.go | 82 +++++++++++------------ client/internal/pqkem/kem.go | 14 ++-- client/internal/pqkem/kem_test.go | 12 ++-- client/internal/pqkem/manager.go | 95 ++++++++++++++------------- client/internal/pqkem/manager_test.go | 10 +-- client/internal/pqkem/message.go | 8 +-- client/internal/pqkem/message_test.go | 2 +- 8 files changed, 124 insertions(+), 124 deletions(-) diff --git a/client/internal/pqkem/callbacks.go b/client/internal/pqkem/callbacks.go index c1578f64a..9dbb8e4bb 100644 --- a/client/internal/pqkem/callbacks.go +++ b/client/internal/pqkem/callbacks.go @@ -1,19 +1,18 @@ package pqkem -// WGCallbackHandler is implemented by the host (the NetBird wiring layer) and -// invoked by the library. The library only reports events; the host owns the -// WireGuard reaction. Keeping this an interface — rather than calling wgctrl -// directly — is what lets the KEM code be extracted as a standalone library. -type WGCallbackHandler interface { - // OnNewPSKReady fires when a fresh post-quantum PSK has been derived for a - // peer and must be programmed on the WireGuard interface. It is invoked at the - // commit point of each side: the initiator on receiving the answer, the +// CallbackHandler is implemented by the host and invoked by the library. The +// library only reports events; the host owns the reaction. Keeping this an +// interface — rather than touching the transport or keying directly — is what lets +// the KEM code be extracted as a standalone library. +type CallbackHandler interface { + // OnNewPSKReady fires when a fresh post-quantum PSK has been derived for a peer + // and must be programmed into the consumer's secure channel. It is invoked at + // the commit point of each side: the initiator on receiving the answer, the // responder on receiving the confirm. - OnNewPSKReady(remoteWgKey string, psk PSK) error + OnNewPSKReady(remoteID string, psk PSK) error // OnRekeyFailed fires when an exchange fails to converge within the allotted - // time. The host should tear the peer connection down so ICE re-establishes, - // and log a WARN. The library reports the event; it does not dictate the - // reaction. - OnRekeyFailed(remoteWgKey string) error + // time. The host should tear the peer connection down so it re-establishes, and + // log a WARN. The library reports the event; it does not dictate the reaction. + OnRekeyFailed(remoteID string) error } diff --git a/client/internal/pqkem/convergence.go b/client/internal/pqkem/convergence.go index 63c0ba136..16d6bec59 100644 --- a/client/internal/pqkem/convergence.go +++ b/client/internal/pqkem/convergence.go @@ -8,9 +8,9 @@ import ( // handleOffer (responder) derives and sends the answer for a new exchange, or // resends the cached answer for a duplicate offer (same exchangeID) without // re-deriving. The responder is purely reactive: no retransmit loop, no deadline. -func (m *Manager) handleOffer(remoteWgKey string, o *OfferMsg) error { +func (m *Manager) handleOffer(remoteID string, o *OfferMsg) error { m.mu.Lock() - if ex := m.exchanges[remoteWgKey]; ex != nil && ex.id == o.ExchangeID { + if ex := m.exchanges[remoteID]; ex != nil && ex.id == o.ExchangeID { state, last := ex.state, ex.lastSent m.mu.Unlock() if state == stateReserved { @@ -18,13 +18,13 @@ func (m *Manager) handleOffer(remoteWgKey string, o *OfferMsg) error { // dropping avoids a second (randomized -> divergent) derivation. return nil } - return m.transport.Send(remoteWgKey, last) + return m.transport.Send(remoteID, last) } // Reserve the slot so a concurrent duplicate offer bails. - m.exchanges[remoteWgKey] = &exchangeCtl{id: o.ExchangeID, state: stateReserved, startedAt: time.Now()} + m.exchanges[remoteID] = &exchangeCtl{id: o.ExchangeID, state: stateReserved, startedAt: time.Now()} m.mu.Unlock() - answerBytes, psk, err := Respond(o.KEMOffer, m.binding(remoteWgKey)) + answerBytes, psk, err := Respond(o.KEMOffer, m.binding(remoteID)) if err != nil { return err } @@ -34,22 +34,22 @@ func (m *Manager) handleOffer(remoteWgKey string, o *OfferMsg) error { } m.mu.Lock() - if ex := m.exchanges[remoteWgKey]; ex != nil && ex.id == o.ExchangeID { + if ex := m.exchanges[remoteID]; ex != nil && ex.id == o.ExchangeID { ex.state = stateAwaitingConfirm ex.lastSent = raw ex.pendingPSK = psk } m.mu.Unlock() - return m.transport.Send(remoteWgKey, raw) + return m.transport.Send(remoteID, raw) } // handleAnswer (initiator) derives the PSK, surfaces it, and sends the confirm, then // switches the retransmit payload to the confirm. Only valid in stateAwaitingAnswer; // advancing the state under the lock makes a concurrent/duplicate answer bail. -func (m *Manager) handleAnswer(remoteWgKey string, a *AnswerMsg) error { +func (m *Manager) handleAnswer(remoteID string, a *AnswerMsg) error { m.mu.Lock() - ex := m.exchanges[remoteWgKey] + ex := m.exchanges[remoteID] if ex == nil || ex.id != a.ExchangeID || ex.state != stateAwaitingAnswer { m.mu.Unlock() return nil @@ -59,11 +59,11 @@ func (m *Manager) handleAnswer(remoteWgKey string, a *AnswerMsg) error { ex.initiator = nil m.mu.Unlock() - psk, err := init.Finish(a.KEMAnswer, m.binding(remoteWgKey)) + psk, err := init.Finish(a.KEMAnswer, m.binding(remoteID)) if err != nil { return err } - if err := m.wg.OnNewPSKReady(remoteWgKey, psk); err != nil { + if err := m.cbHandler.OnNewPSKReady(remoteID, psk); err != nil { return err } raw, err := (&ConfirmMsg{ExchangeID: a.ExchangeID}).Encode() @@ -72,42 +72,42 @@ func (m *Manager) handleAnswer(remoteWgKey string, a *AnswerMsg) error { } m.mu.Lock() - if ex := m.exchanges[remoteWgKey]; ex != nil && ex.id == a.ExchangeID { + if ex := m.exchanges[remoteID]; ex != nil && ex.id == a.ExchangeID { ex.lastSent = raw // loop now retransmits the confirm - m.established[remoteWgKey] = true - m.failures[remoteWgKey] = 0 + m.established[remoteID] = true + m.failures[remoteID] = 0 _ = time.Since(ex.startedAt) // convergence latency (metrics hook, later step) } m.mu.Unlock() - return m.transport.Send(remoteWgKey, raw) + return m.transport.Send(remoteID, raw) } // handleConfirm (responder) commits the pending PSK. Only valid in // stateAwaitingConfirm; duplicate confirms (the initiator best-effort resends it) // find the exchange gone and are ignored. -func (m *Manager) handleConfirm(remoteWgKey string, c *ConfirmMsg) error { +func (m *Manager) handleConfirm(remoteID string, c *ConfirmMsg) error { m.mu.Lock() - ex := m.exchanges[remoteWgKey] + ex := m.exchanges[remoteID] if ex == nil || ex.id != c.ExchangeID || ex.state != stateAwaitingConfirm { m.mu.Unlock() return nil } psk := ex.pendingPSK - delete(m.exchanges, remoteWgKey) - m.established[remoteWgKey] = true - m.failures[remoteWgKey] = 0 + delete(m.exchanges, remoteID) + m.established[remoteID] = true + m.failures[remoteID] = 0 _ = time.Since(ex.startedAt) // convergence latency (metrics hook, later step) m.mu.Unlock() - return m.wg.OnNewPSKReady(remoteWgKey, psk) + return m.cbHandler.OnNewPSKReady(remoteID, psk) } // initiatorLoop retransmits the initiator's outstanding message, keyed off the // exchange state: the offer while awaiting the answer (bounded by maxRetries -> // failure), then the confirm a few best-effort times before stopping. The // convergence deadline is thus derived from maxRetries * retryInterval. -func (m *Manager) initiatorLoop(ctx context.Context, remoteWgKey string, id ExchangeID) { +func (m *Manager) initiatorLoop(ctx context.Context, remoteID string, id ExchangeID) { defer m.wait.Done() t := time.NewTicker(m.retryInterval) defer t.Stop() @@ -119,7 +119,7 @@ func (m *Manager) initiatorLoop(ctx context.Context, remoteWgKey string, id Exch return case <-t.C: m.mu.Lock() - ex := m.exchanges[remoteWgKey] + ex := m.exchanges[remoteID] if ex == nil || ex.id != id { m.mu.Unlock() return @@ -128,27 +128,27 @@ func (m *Manager) initiatorLoop(ctx context.Context, remoteWgKey string, id Exch switch ex.state { case stateAwaitingAnswer: if offerAttempts >= m.maxRetries { - delete(m.exchanges, remoteWgKey) - fail := m.registerFailureLocked(remoteWgKey) + delete(m.exchanges, remoteID) + fail := m.registerFailureLocked(remoteID) m.mu.Unlock() - m.raiseFailure(remoteWgKey, fail) + m.raiseFailure(remoteID, fail) return } msg := ex.lastSent offerAttempts++ m.mu.Unlock() - m.retransmit(remoteWgKey, msg) + m.retransmit(remoteID, msg) case stateConfirming: if confirmsSent >= confirmRetransmits { - delete(m.exchanges, remoteWgKey) + delete(m.exchanges, remoteID) m.mu.Unlock() return } msg := ex.lastSent confirmsSent++ m.mu.Unlock() - m.retransmit(remoteWgKey, msg) + m.retransmit(remoteID, msg) default: m.mu.Unlock() @@ -162,30 +162,30 @@ func (m *Manager) initiatorLoop(ctx context.Context, remoteWgKey string, id Exch // an initial exchange (peer never established) fails immediately; a rekey tolerates // up to maxRekeyFailures consecutive misses (we stay on the still-valid previous // PSK) before failing. Assumes m.mu is held. -func (m *Manager) registerFailureLocked(remoteWgKey string) bool { - if !m.established[remoteWgKey] { +func (m *Manager) registerFailureLocked(remoteID string) bool { + if !m.established[remoteID] { return true } - m.failures[remoteWgKey]++ - if m.failures[remoteWgKey] >= m.maxRekeyFailures { - m.failures[remoteWgKey] = 0 + m.failures[remoteID]++ + if m.failures[remoteID] >= m.maxRekeyFailures { + m.failures[remoteID] = 0 return true } return false } -func (m *Manager) raiseFailure(remoteWgKey string, fail bool) { +func (m *Manager) raiseFailure(remoteID string, fail bool) { if !fail { - m.logger.Warn("pqkem rekey attempt timed out, will retry next cycle", "peer", remoteWgKey) + m.logger.Warn("pqkem rekey attempt timed out, will retry next cycle", "peer", remoteID) return } - if err := m.wg.OnRekeyFailed(remoteWgKey); err != nil { - m.logger.Error("pqkem OnRekeyFailed handler error", "peer", remoteWgKey, "err", err) + if err := m.cbHandler.OnRekeyFailed(remoteID); err != nil { + m.logger.Error("pqkem OnRekeyFailed handler error", "peer", remoteID, "err", err) } } -func (m *Manager) retransmit(remoteWgKey string, msg []byte) { - if err := m.transport.Send(remoteWgKey, msg); err != nil { - m.logger.Warn("pqkem retransmit failed", "peer", remoteWgKey, "err", err) +func (m *Manager) retransmit(remoteID string, msg []byte) { + if err := m.transport.Send(remoteID, msg); err != nil { + m.logger.Warn("pqkem retransmit failed", "peer", remoteID, "err", err) } } diff --git a/client/internal/pqkem/kem.go b/client/internal/pqkem/kem.go index c2961eced..e7e855cd1 100644 --- a/client/internal/pqkem/kem.go +++ b/client/internal/pqkem/kem.go @@ -1,6 +1,6 @@ // Package pqkem is a spike (NET-1406) for a post-quantum pre-shared-key exchange // that could replace Rosenpass. It performs an X25519MLKEM768 hybrid key -// encapsulation and derives a 32-byte WireGuard PSK. +// encapsulation and derives a 32-byte pre-shared key (PSK). // // The exchange is a single round trip designed to ride the (already // authenticated) Signal offer/answer channel: @@ -9,7 +9,7 @@ // initiator <--Answer(1120B)-- responder // // Both sides then hold the same PSK, which is bound to the two peers' identities -// (their WireGuard static public keys) so the derived key cannot be transplanted +// (their peer identity keys) so the derived key cannot be transplanted // to a different peer pair even if the transport authentication were bypassed. // // Combiner note: this follows the IETF hybrid layout (X25519 ‖ ML-KEM on the @@ -35,14 +35,14 @@ const ( pskLabel = "netbird-pq-psk-v1" ) -// PSK is the 32-byte pre-shared key handed to WireGuard. +// PSK is the 32-byte derived pre-shared key handed to the consumer to key its channel. type PSK [32]byte // Binding identifies the peer pair the PSK is derived for. Callers set both -// WireGuard static public keys; the order does not matter (it is canonicalised). +// peer identity keys; the order does not matter (it is canonicalised). type Binding struct { - LocalWgPub []byte - RemoteWgPub []byte + LocalID []byte + RemoteID []byte } // Initiator holds the ephemeral secrets between Offer and Finish. @@ -141,7 +141,7 @@ func Respond(offer []byte, b Binding) (answer []byte, psk PSK, err error) { // TODO(NET-1406): replace the SHA-256 concat with the RFC HKDF combiner // (crypto/hkdf, Go 1.24+) and proper labels before this leaves spike status. func derivePSK(ssMLKEM, ssX, offer, answer []byte, b Binding) PSK { - lo, hi := canonicalPair(b.LocalWgPub, b.RemoteWgPub) + lo, hi := canonicalPair(b.LocalID, b.RemoteID) h := sha256.New() h.Write([]byte(pskLabel)) diff --git a/client/internal/pqkem/kem_test.go b/client/internal/pqkem/kem_test.go index 3bdc99635..499c82470 100644 --- a/client/internal/pqkem/kem_test.go +++ b/client/internal/pqkem/kem_test.go @@ -18,11 +18,11 @@ func TestExchange_DerivesMatchingPSK(t *testing.T) { require.Len(t, init.Offer(), OfferSize) - answer, pskB, err := Respond(init.Offer(), Binding{LocalWgPub: wgB, RemoteWgPub: wgA}) + answer, pskB, err := Respond(init.Offer(), Binding{LocalID: wgB, RemoteID: wgA}) require.NoError(t, err) require.Len(t, answer, AnswerSize) - pskA, err := init.Finish(answer, Binding{LocalWgPub: wgA, RemoteWgPub: wgB}) + pskA, err := init.Finish(answer, Binding{LocalID: wgA, RemoteID: wgB}) require.NoError(t, err) require.Equal(t, pskB, pskA, "both sides must derive the same PSK") @@ -34,13 +34,13 @@ func TestExchange_PSKBoundToPeerIdentities(t *testing.T) { require.NoError(t, err) // responder computes with the honest pair... - _, pskHonest, err := Respond(init.Offer(), Binding{LocalWgPub: wgB, RemoteWgPub: wgA}) + _, pskHonest, err := Respond(init.Offer(), Binding{LocalID: wgB, RemoteID: wgA}) require.NoError(t, err) // ...a second responder run with a different peer identity yields a different PSK, // even though the KEM material would otherwise combine identically. wgC := []byte("peer-C-wireguard-pubkey-32bytes!") - _, pskWrong, err := Respond(init.Offer(), Binding{LocalWgPub: wgC, RemoteWgPub: wgA}) + _, pskWrong, err := Respond(init.Offer(), Binding{LocalID: wgC, RemoteID: wgA}) require.NoError(t, err) require.NotEqual(t, pskHonest, pskWrong, "PSK must be bound to the peer pair") @@ -70,12 +70,12 @@ func TestExchange_ReportSizesAndTiming(t *testing.T) { tInit += time.Since(s0) s1 := time.Now() - answer, _, err := Respond(init.Offer(), Binding{LocalWgPub: wgB, RemoteWgPub: wgA}) + answer, _, err := Respond(init.Offer(), Binding{LocalID: wgB, RemoteID: wgA}) require.NoError(t, err) tResp += time.Since(s1) s2 := time.Now() - _, err = init.Finish(answer, Binding{LocalWgPub: wgA, RemoteWgPub: wgB}) + _, err = init.Finish(answer, Binding{LocalID: wgA, RemoteID: wgB}) require.NoError(t, err) tFinish += time.Since(s2) } diff --git a/client/internal/pqkem/manager.go b/client/internal/pqkem/manager.go index cd48866f0..732fdbec5 100644 --- a/client/internal/pqkem/manager.go +++ b/client/internal/pqkem/manager.go @@ -10,8 +10,9 @@ import ( ) const ( - // DefaultRekeyInterval matches WireGuard's own REKEY_AFTER_TIME so the freshly - // rotated PSK is naturally adopted by WG's next handshake without forcing one. + // DefaultRekeyInterval is the default PSK rotation cadence (~2 min), chosen so a + // rotated PSK is adopted by the consumer's next transport handshake without + // forcing one. DefaultRekeyInterval = 2 * time.Minute // DefaultRetryInterval is how often the initiator retransmits its outstanding // message (offer, then confirm) while an exchange is in flight. @@ -29,11 +30,11 @@ const ( ) // Transport hands an already-encoded exchange message to the peer. The host routes -// it over the appropriate channel — Signal before the tunnel is up, the WireGuard -// tunnel for rekeys — so the Manager never needs to know which is in use. It is the -// analogue of go-rosenpass's Conn seam. +// it over the appropriate channel — a signalling channel before the tunnel is up, +// the data tunnel for rekeys — so the Manager never needs to know which is in use. +// It is the analogue of go-rosenpass's Conn seam. type Transport interface { - Send(remoteWgKey string, msg []byte) error + Send(remoteID string, msg []byte) error } // exchangeState is the single source of truth for an exchange's role and phase. @@ -66,14 +67,14 @@ type exchangeCtl struct { // Manager is the stateful orchestrator — the analogue of go-rosenpass's Server. It // runs the per-peer rekey timer, drives the X25519MLKEM768 exchange over a pluggable -// Transport, and surfaces the derived PSK to the host via WGCallbackHandler. The +// Transport, and surfaces the derived PSK to the host via CallbackHandler. The // cryptography is the pure kem.go primitives; all per-exchange and per-peer state // lives here under one lock. type Manager struct { - localWgKey string - transport Transport - wg WGCallbackHandler - logger *slog.Logger + localID string + transport Transport + cbHandler CallbackHandler + logger *slog.Logger rekeyInterval time.Duration retryInterval time.Duration @@ -91,11 +92,11 @@ type Manager struct { wait sync.WaitGroup } -// NewManager builds a manager for the local peer identified by its WireGuard public +// NewManager builds a manager for the local peer identified by its peer identity // key (used for the deterministic initiator role and the identity binding). A zero // interval falls back to DefaultRekeyInterval; a nil logger to slog.Default(). // Retry/retries/K use their defaults and can be overridden before use. -func NewManager(localWgKey string, t Transport, h WGCallbackHandler, interval time.Duration, logger *slog.Logger) *Manager { +func NewManager(localID string, t Transport, h CallbackHandler, interval time.Duration, logger *slog.Logger) *Manager { if interval <= 0 { interval = DefaultRekeyInterval } @@ -104,9 +105,9 @@ func NewManager(localWgKey string, t Transport, h WGCallbackHandler, interval ti } ctx, cancel := context.WithCancel(context.Background()) return &Manager{ - localWgKey: localWgKey, + localID: localID, transport: t, - wg: h, + cbHandler: h, logger: logger, rekeyInterval: interval, retryInterval: DefaultRetryInterval, @@ -122,40 +123,40 @@ func NewManager(localWgKey string, t Transport, h WGCallbackHandler, interval ti } // IsInitiator reports whether the local peer drives the exchange for this remote -// peer. Roles are deterministic (lexicographic WG-key compare) so exactly one side -// initiates, mirroring how Rosenpass picks its handshake initiator. -func (m *Manager) IsInitiator(remoteWgKey string) bool { - return m.localWgKey > remoteWgKey +// peer. Roles are deterministic (lexicographic identity-key compare) so exactly one +// side initiates, mirroring how Rosenpass picks its handshake initiator. +func (m *Manager) IsInitiator(remoteID string) bool { + return m.localID > remoteID } // AddPeer registers a remote peer and starts its rekey timer. Re-adding is a no-op. -func (m *Manager) AddPeer(remoteWgKey string) { +func (m *Manager) AddPeer(remoteID string) { m.mu.Lock() defer m.mu.Unlock() - if _, ok := m.peers[remoteWgKey]; ok { + if _, ok := m.peers[remoteID]; ok { return } ctx, cancel := context.WithCancel(m.rootCtx) - m.peers[remoteWgKey] = cancel + m.peers[remoteID] = cancel m.wait.Add(1) - go m.rekeyLoop(ctx, remoteWgKey) + go m.rekeyLoop(ctx, remoteID) } // RemovePeer stops a peer's rekey timer and any in-flight exchange, and drops state. -func (m *Manager) RemovePeer(remoteWgKey string) { +func (m *Manager) RemovePeer(remoteID string) { m.mu.Lock() - if cancel, ok := m.peers[remoteWgKey]; ok { + if cancel, ok := m.peers[remoteID]; ok { cancel() - delete(m.peers, remoteWgKey) + delete(m.peers, remoteID) } - if ex, ok := m.exchanges[remoteWgKey]; ok { + if ex, ok := m.exchanges[remoteID]; ok { if ex.cancel != nil { ex.cancel() } - delete(m.exchanges, remoteWgKey) + delete(m.exchanges, remoteID) } - delete(m.established, remoteWgKey) - delete(m.failures, remoteWgKey) + delete(m.established, remoteID) + delete(m.failures, remoteID) m.mu.Unlock() } @@ -171,28 +172,28 @@ func (m *Manager) Stop() { // HandleInbound decodes an incoming message and drives the exchange, sending any // response via the transport and surfacing derived PSKs / convergence to the host. -func (m *Manager) HandleInbound(remoteWgKey string, raw []byte) error { +func (m *Manager) HandleInbound(remoteID string, raw []byte) error { typ, msg, err := Decode(raw) if err != nil { - return fmt.Errorf("decode from %s: %w", remoteWgKey, err) + return fmt.Errorf("decode from %s: %w", remoteID, err) } switch typ { case MsgOffer: - return m.handleOffer(remoteWgKey, msg.(*OfferMsg)) + return m.handleOffer(remoteID, msg.(*OfferMsg)) case MsgAnswer: - return m.handleAnswer(remoteWgKey, msg.(*AnswerMsg)) + return m.handleAnswer(remoteID, msg.(*AnswerMsg)) case MsgConfirm: - return m.handleConfirm(remoteWgKey, msg.(*ConfirmMsg)) + return m.handleConfirm(remoteID, msg.(*ConfirmMsg)) default: - return fmt.Errorf("unhandled message type %d from %s", typ, remoteWgKey) + return fmt.Errorf("unhandled message type %d from %s", typ, remoteID) } } // initiateRekey starts a fresh exchange when the local peer is the initiator for // this remote peer; the responder waits for the offer instead. Exposed (unexported // but directly callable) so tests can drive a rekey without waiting on the ticker. -func (m *Manager) initiateRekey(remoteWgKey string) error { - if !m.IsInitiator(remoteWgKey) { +func (m *Manager) initiateRekey(remoteID string) error { + if !m.IsInitiator(remoteID) { return nil } init, err := NewInitiator() @@ -210,10 +211,10 @@ func (m *Manager) initiateRekey(remoteWgKey string) error { ctx, cancel := context.WithCancel(m.rootCtx) m.mu.Lock() - if old := m.exchanges[remoteWgKey]; old != nil && old.cancel != nil { + if old := m.exchanges[remoteID]; old != nil && old.cancel != nil { old.cancel() } - m.exchanges[remoteWgKey] = &exchangeCtl{ + m.exchanges[remoteID] = &exchangeCtl{ id: id, state: stateAwaitingAnswer, startedAt: time.Now(), @@ -224,12 +225,12 @@ func (m *Manager) initiateRekey(remoteWgKey string) error { m.mu.Unlock() m.wait.Add(1) - go m.initiatorLoop(ctx, remoteWgKey, id) + go m.initiatorLoop(ctx, remoteID, id) - return m.transport.Send(remoteWgKey, raw) + return m.transport.Send(remoteID, raw) } -func (m *Manager) rekeyLoop(ctx context.Context, remoteWgKey string) { +func (m *Manager) rekeyLoop(ctx context.Context, remoteID string) { defer m.wait.Done() t := time.NewTicker(m.rekeyInterval) defer t.Stop() @@ -238,15 +239,15 @@ func (m *Manager) rekeyLoop(ctx context.Context, remoteWgKey string) { case <-ctx.Done(): return case <-t.C: - if err := m.initiateRekey(remoteWgKey); err != nil { - m.logger.Error("pqkem rekey failed to start", "peer", remoteWgKey, "err", err) + if err := m.initiateRekey(remoteID); err != nil { + m.logger.Error("pqkem rekey failed to start", "peer", remoteID, "err", err) } } } } -func (m *Manager) binding(remoteWgKey string) Binding { - return Binding{LocalWgPub: []byte(m.localWgKey), RemoteWgPub: []byte(remoteWgKey)} +func (m *Manager) binding(remoteID string) Binding { + return Binding{LocalID: []byte(m.localID), RemoteID: []byte(remoteID)} } func newExchangeID() (ExchangeID, error) { diff --git a/client/internal/pqkem/manager_test.go b/client/internal/pqkem/manager_test.go index e355b17fa..1100f8321 100644 --- a/client/internal/pqkem/manager_test.go +++ b/client/internal/pqkem/manager_test.go @@ -15,7 +15,7 @@ type loopback struct { peer *Manager } -func (l *loopback) Send(remoteWgKey string, msg []byte) error { +func (l *loopback) Send(remoteID string, msg []byte) error { cp := append([]byte(nil), msg...) return l.peer.HandleInbound(l.localKey, cp) } @@ -28,17 +28,17 @@ type fakeWG struct { func newFakeWG() *fakeWG { return &fakeWG{psks: map[string]PSK{}} } -func (f *fakeWG) OnNewPSKReady(remoteWgKey string, psk PSK) error { +func (f *fakeWG) OnNewPSKReady(remoteID string, psk PSK) error { f.mu.Lock() defer f.mu.Unlock() - f.psks[remoteWgKey] = psk + f.psks[remoteID] = psk return nil } -func (f *fakeWG) OnRekeyFailed(remoteWgKey string) error { +func (f *fakeWG) OnRekeyFailed(remoteID string) error { f.mu.Lock() defer f.mu.Unlock() - f.failed = append(f.failed, remoteWgKey) + f.failed = append(f.failed, remoteID) return nil } diff --git a/client/internal/pqkem/message.go b/client/internal/pqkem/message.go index fe4381636..d1c6e69a4 100644 --- a/client/internal/pqkem/message.go +++ b/client/internal/pqkem/message.go @@ -7,7 +7,7 @@ import ( // Wire framing for the PQ-KEM exchange. Messages are self-contained, versioned, // transport-agnostic byte blobs: the same bytes ride the Signal offer/answer -// (initial, pre-tunnel) or a WireGuard-tunnel packet (rekey). They are NOT a gRPC +// (initial, pre-tunnel) or a data-tunnel packet (rekey). They are NOT a gRPC // service — the network layer only sees opaque []byte. // // Layout (all messages): [type:1][version:1][exchangeID:16][payload...] @@ -59,9 +59,9 @@ type AnswerMsg struct { // derive it to encapsulate), so the initiator needs no confirmation. Only the // responder is left unsure whether the initiator received the answer and committed // the key — this message resolves that. As a bonus, being sent under the new PSK it -// triggers the WireGuard handshake with the new key: the responder converges on -// receiving it, and the initiator converges by observing that handshake succeed -// (which fails on a PSK mismatch, so success proves the responder also committed). +// exercises the consumer's channel handshake with the new key: the responder +// converges on receiving it, and the initiator converges by observing that handshake +// succeed (which fails on a PSK mismatch, so success proves the responder committed too). type ConfirmMsg struct { ExchangeID ExchangeID } diff --git a/client/internal/pqkem/message_test.go b/client/internal/pqkem/message_test.go index 1845bc4d9..8708695d5 100644 --- a/client/internal/pqkem/message_test.go +++ b/client/internal/pqkem/message_test.go @@ -9,7 +9,7 @@ import ( func TestMessageRoundTrip(t *testing.T) { init, err := NewInitiator() require.NoError(t, err) - answer, _, err := Respond(init.Offer(), Binding{LocalWgPub: wgB, RemoteWgPub: wgA}) + answer, _, err := Respond(init.Offer(), Binding{LocalID: wgB, RemoteID: wgA}) require.NoError(t, err) id := ExchangeID{1, 2, 3, 4}