mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-22 06:39:08 +02:00
Rework signal protocol to minimize number of exchanged initial RP 512KB keys
This commit is contained in:
@@ -65,6 +65,20 @@ type WgConfig struct {
|
||||
PreSharedKey *wgtypes.Key
|
||||
}
|
||||
|
||||
// RosenpassKeyResolver lets the handshaker fill the fingerprint/cache fields of an
|
||||
// offer/answer without depending on the Rosenpass manager directly. Implemented by
|
||||
// rosenpass.Manager and wired in by the engine.
|
||||
type RosenpassKeyResolver interface {
|
||||
// LocalPubKeyHash is the SHA256 of our own Rosenpass public key.
|
||||
LocalPubKeyHash() []byte
|
||||
// RemotePubKeyAck is the SHA256 of the remote peer's cached key (nil if we do
|
||||
// not hold it), sent back as an acknowledgement.
|
||||
RemotePubKeyAck(remoteWgKey string) []byte
|
||||
// RemoteHasLocalKey reports whether the peer already holds our key, so the full
|
||||
// key may be omitted.
|
||||
RemoteHasLocalKey(remoteWgKey string) bool
|
||||
}
|
||||
|
||||
type RosenpassConfig struct {
|
||||
// RosenpassPubKey is this peer's Rosenpass public key
|
||||
PubKey []byte
|
||||
@@ -72,6 +86,10 @@ type RosenpassConfig struct {
|
||||
Addr string
|
||||
|
||||
PermissiveMode bool
|
||||
|
||||
// KeyResolver drives fingerprint-based key caching over signalling. Nil when
|
||||
// Rosenpass is disabled, which makes the handshaker always send the full key.
|
||||
KeyResolver RosenpassKeyResolver
|
||||
}
|
||||
|
||||
// ConnConfig is a peer Connection configuration
|
||||
|
||||
@@ -33,8 +33,13 @@ type OfferAnswer struct {
|
||||
// Version of NetBird Agent
|
||||
Version string
|
||||
// RosenpassPubKey is the Rosenpass public key of the remote peer when receiving this message
|
||||
// This value is the local Rosenpass server public key when sending the message
|
||||
// This value is the local Rosenpass server public key when sending the message.
|
||||
// May be empty on send when the remote peer has acknowledged already holding it (see RosenpassPubKeyAck).
|
||||
RosenpassPubKey []byte
|
||||
// RosenpassPubKeyHash is the SHA256 of the sender's own RosenpassPubKey. Always set when Rosenpass is enabled.
|
||||
RosenpassPubKeyHash []byte
|
||||
// RosenpassPubKeyAck is the SHA256 of the remote peer's key the sender holds cached; empty means "send it in full".
|
||||
RosenpassPubKeyAck []byte
|
||||
// RosenpassAddr is the Rosenpass server address (IP:port) of the remote peer when receiving this message
|
||||
// This value is the local Rosenpass server address when sending the message
|
||||
RosenpassAddr string
|
||||
@@ -209,11 +214,11 @@ func (h *Handshaker) sendAnswer() error {
|
||||
|
||||
func (h *Handshaker) buildOfferAnswer() OfferAnswer {
|
||||
answer := OfferAnswer{
|
||||
WgListenPort: h.config.LocalWgPort,
|
||||
Version: version.NetbirdVersion(),
|
||||
RosenpassPubKey: h.config.RosenpassConfig.PubKey,
|
||||
RosenpassAddr: h.config.RosenpassConfig.Addr,
|
||||
WgListenPort: h.config.LocalWgPort,
|
||||
Version: version.NetbirdVersion(),
|
||||
RosenpassAddr: h.config.RosenpassConfig.Addr,
|
||||
}
|
||||
h.setRosenpassPubKey(&answer)
|
||||
|
||||
if h.ice != nil && h.RemoteICESupported() {
|
||||
uFrag, pwd := h.ice.GetLocalUserCredentials()
|
||||
@@ -230,6 +235,30 @@ func (h *Handshaker) buildOfferAnswer() OfferAnswer {
|
||||
return answer
|
||||
}
|
||||
|
||||
// setRosenpassPubKey fills the Rosenpass key fields of an outgoing offer/answer.
|
||||
// With a resolver wired it advertises our key hash and the ack for the remote key
|
||||
// we hold, and includes the full public key only when the peer has not yet
|
||||
// acknowledged holding it. Without a resolver (Rosenpass disabled, or an older
|
||||
// code path) it always sends the full key, preserving the previous behaviour.
|
||||
func (h *Handshaker) setRosenpassPubKey(answer *OfferAnswer) {
|
||||
localKey := h.config.RosenpassConfig.PubKey
|
||||
if len(localKey) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
resolver := h.config.RosenpassConfig.KeyResolver
|
||||
if resolver == nil {
|
||||
answer.RosenpassPubKey = localKey
|
||||
return
|
||||
}
|
||||
|
||||
answer.RosenpassPubKeyHash = resolver.LocalPubKeyHash()
|
||||
answer.RosenpassPubKeyAck = resolver.RemotePubKeyAck(h.config.Key)
|
||||
if !resolver.RemoteHasLocalKey(h.config.Key) {
|
||||
answer.RosenpassPubKey = localKey
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handshaker) updateRemoteICEState(offer *OfferAnswer) {
|
||||
hasICE := offer.hasICECredentials()
|
||||
prev := h.remoteICESupported.Swap(hasICE)
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type fakeRPResolver struct {
|
||||
localHash []byte
|
||||
ack []byte
|
||||
hasLocal bool
|
||||
}
|
||||
|
||||
func (f fakeRPResolver) LocalPubKeyHash() []byte { return f.localHash }
|
||||
func (f fakeRPResolver) RemotePubKeyAck(string) []byte { return f.ack }
|
||||
func (f fakeRPResolver) RemoteHasLocalKey(remote string) bool { return f.hasLocal }
|
||||
|
||||
func TestSetRosenpassPubKey_NoResolverAlwaysSendsFullKey(t *testing.T) {
|
||||
localKey := []byte{1, 2, 3}
|
||||
h := &Handshaker{config: ConnConfig{RosenpassConfig: RosenpassConfig{PubKey: localKey}}}
|
||||
|
||||
var a OfferAnswer
|
||||
h.setRosenpassPubKey(&a)
|
||||
|
||||
require.Equal(t, localKey, a.RosenpassPubKey)
|
||||
require.Nil(t, a.RosenpassPubKeyHash)
|
||||
require.Nil(t, a.RosenpassPubKeyAck)
|
||||
}
|
||||
|
||||
func TestSetRosenpassPubKey_ResolverIncludesFullKeyUntilAcked(t *testing.T) {
|
||||
localKey := []byte{1, 2, 3}
|
||||
res := fakeRPResolver{localHash: []byte{9}, ack: []byte{8}, hasLocal: false}
|
||||
h := &Handshaker{config: ConnConfig{Key: "peerA", RosenpassConfig: RosenpassConfig{PubKey: localKey, KeyResolver: res}}}
|
||||
|
||||
var a OfferAnswer
|
||||
h.setRosenpassPubKey(&a)
|
||||
|
||||
require.Equal(t, localKey, a.RosenpassPubKey, "full key must be sent until the peer acks it")
|
||||
require.Equal(t, []byte{9}, a.RosenpassPubKeyHash)
|
||||
require.Equal(t, []byte{8}, a.RosenpassPubKeyAck)
|
||||
}
|
||||
|
||||
func TestSetRosenpassPubKey_ResolverOmitsFullKeyOnceAcked(t *testing.T) {
|
||||
localKey := []byte{1, 2, 3}
|
||||
res := fakeRPResolver{localHash: []byte{9}, ack: []byte{8}, hasLocal: true}
|
||||
h := &Handshaker{config: ConnConfig{Key: "peerA", RosenpassConfig: RosenpassConfig{PubKey: localKey, KeyResolver: res}}}
|
||||
|
||||
var a OfferAnswer
|
||||
h.setRosenpassPubKey(&a)
|
||||
|
||||
require.Nil(t, a.RosenpassPubKey, "full key must be omitted once the peer holds it")
|
||||
require.Equal(t, []byte{9}, a.RosenpassPubKeyHash)
|
||||
require.Equal(t, []byte{8}, a.RosenpassPubKeyAck)
|
||||
}
|
||||
|
||||
func TestSetRosenpassPubKey_DisabledSetsNothing(t *testing.T) {
|
||||
h := &Handshaker{config: ConnConfig{RosenpassConfig: RosenpassConfig{}}}
|
||||
|
||||
var a OfferAnswer
|
||||
h.setRosenpassPubKey(&a)
|
||||
|
||||
require.Nil(t, a.RosenpassPubKey)
|
||||
require.Nil(t, a.RosenpassPubKeyHash)
|
||||
}
|
||||
@@ -61,11 +61,13 @@ func (s *Signaler) signalOfferAnswer(offerAnswer OfferAnswer, remoteKey string,
|
||||
UFrag: offerAnswer.IceCredentials.UFrag,
|
||||
Pwd: offerAnswer.IceCredentials.Pwd,
|
||||
},
|
||||
RosenpassPubKey: offerAnswer.RosenpassPubKey,
|
||||
RosenpassAddr: offerAnswer.RosenpassAddr,
|
||||
RelaySrvAddress: offerAnswer.RelaySrvAddress,
|
||||
RelaySrvIP: offerAnswer.RelaySrvIP,
|
||||
SessionID: sessionIDBytes,
|
||||
RosenpassPubKey: offerAnswer.RosenpassPubKey,
|
||||
RosenpassPubKeyHash: offerAnswer.RosenpassPubKeyHash,
|
||||
RosenpassPubKeyAck: offerAnswer.RosenpassPubKeyAck,
|
||||
RosenpassAddr: offerAnswer.RosenpassAddr,
|
||||
RelaySrvAddress: offerAnswer.RelaySrvAddress,
|
||||
RelaySrvIP: offerAnswer.RelaySrvIP,
|
||||
SessionID: sessionIDBytes,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
Reference in New Issue
Block a user