mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-20 20:39:08 +02:00
Rework signal protocol to minimize number of exchanged initial RP 512KB keys
This commit is contained in:
@@ -1809,6 +1809,7 @@ func (e *Engine) createPeerConn(pubKey string, allowedIPs []netip.Prefix, agentV
|
|||||||
PubKey: e.getRosenpassPubKey(),
|
PubKey: e.getRosenpassPubKey(),
|
||||||
Addr: e.getRosenpassAddr(),
|
Addr: e.getRosenpassAddr(),
|
||||||
PermissiveMode: e.config.RosenpassPermissive,
|
PermissiveMode: e.config.RosenpassPermissive,
|
||||||
|
KeyResolver: e.rosenpassKeyResolver(),
|
||||||
},
|
},
|
||||||
ICEConfig: e.createICEConfig(),
|
ICEConfig: e.createICEConfig(),
|
||||||
}
|
}
|
||||||
@@ -1879,6 +1880,8 @@ func (e *Engine) receiveSignalEvents() error {
|
|||||||
|
|
||||||
log.Debugf("receiveMSG: took %s to get lock for peer %s with session id %s", gotLock, msg.Key, offerAnswer.SessionID)
|
log.Debugf("receiveMSG: took %s to get lock for peer %s with session id %s", gotLock, msg.Key, offerAnswer.SessionID)
|
||||||
|
|
||||||
|
e.applyRosenpassKeyExchange(msg, offerAnswer)
|
||||||
|
|
||||||
if msg.Body.Type == sProto.Body_OFFER {
|
if msg.Body.Type == sProto.Body_OFFER {
|
||||||
conn.OnRemoteOffer(*offerAnswer)
|
conn.OnRemoteOffer(*offerAnswer)
|
||||||
} else {
|
} else {
|
||||||
@@ -2222,6 +2225,34 @@ func (e *Engine) getRosenpassAddr() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// rosenpassKeyResolver returns the Rosenpass manager as the offer/answer key
|
||||||
|
// resolver, or a true nil interface when Rosenpass is disabled (returning the
|
||||||
|
// typed-nil *Manager would make the interface non-nil and panic on use).
|
||||||
|
func (e *Engine) rosenpassKeyResolver() peer.RosenpassKeyResolver {
|
||||||
|
if e.rpManager == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return e.rpManager
|
||||||
|
}
|
||||||
|
|
||||||
|
// applyRosenpassKeyExchange reconciles the fingerprint/cache fields of an incoming
|
||||||
|
// offer/answer against the Rosenpass manager's cache: it resolves the remote peer's
|
||||||
|
// full public key (from the message or the cache) into the OfferAnswer, and records
|
||||||
|
// whether the peer acknowledged holding our key. No-op when Rosenpass is disabled.
|
||||||
|
func (e *Engine) applyRosenpassKeyExchange(msg *sProto.Message, oa *peer.OfferAnswer) {
|
||||||
|
if e.rpManager == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cfg := msg.GetBody().GetRosenpassConfig()
|
||||||
|
if cfg == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteWgKey := msg.GetKey()
|
||||||
|
oa.RosenpassPubKey = e.rpManager.ResolveRemotePubKey(remoteWgKey, cfg.GetRosenpassPubKey(), cfg.GetRosenpassPubKeyHash())
|
||||||
|
e.rpManager.SetRemoteAck(remoteWgKey, cfg.GetAcknowledgedRosenpassPubKeyHash())
|
||||||
|
}
|
||||||
|
|
||||||
// RunHealthProbes executes health checks for Signal, Management, Relay, and WireGuard services
|
// RunHealthProbes executes health checks for Signal, Management, Relay, and WireGuard services
|
||||||
// and updates the status recorder with the latest states.
|
// and updates the status recorder with the latest states.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -65,6 +65,20 @@ type WgConfig struct {
|
|||||||
PreSharedKey *wgtypes.Key
|
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 {
|
type RosenpassConfig struct {
|
||||||
// RosenpassPubKey is this peer's Rosenpass public key
|
// RosenpassPubKey is this peer's Rosenpass public key
|
||||||
PubKey []byte
|
PubKey []byte
|
||||||
@@ -72,6 +86,10 @@ type RosenpassConfig struct {
|
|||||||
Addr string
|
Addr string
|
||||||
|
|
||||||
PermissiveMode bool
|
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
|
// ConnConfig is a peer Connection configuration
|
||||||
|
|||||||
@@ -33,8 +33,13 @@ type OfferAnswer struct {
|
|||||||
// Version of NetBird Agent
|
// Version of NetBird Agent
|
||||||
Version string
|
Version string
|
||||||
// RosenpassPubKey is the Rosenpass public key of the remote peer when receiving this message
|
// 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
|
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
|
// 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
|
// This value is the local Rosenpass server address when sending the message
|
||||||
RosenpassAddr string
|
RosenpassAddr string
|
||||||
@@ -209,11 +214,11 @@ func (h *Handshaker) sendAnswer() error {
|
|||||||
|
|
||||||
func (h *Handshaker) buildOfferAnswer() OfferAnswer {
|
func (h *Handshaker) buildOfferAnswer() OfferAnswer {
|
||||||
answer := OfferAnswer{
|
answer := OfferAnswer{
|
||||||
WgListenPort: h.config.LocalWgPort,
|
WgListenPort: h.config.LocalWgPort,
|
||||||
Version: version.NetbirdVersion(),
|
Version: version.NetbirdVersion(),
|
||||||
RosenpassPubKey: h.config.RosenpassConfig.PubKey,
|
RosenpassAddr: h.config.RosenpassConfig.Addr,
|
||||||
RosenpassAddr: h.config.RosenpassConfig.Addr,
|
|
||||||
}
|
}
|
||||||
|
h.setRosenpassPubKey(&answer)
|
||||||
|
|
||||||
if h.ice != nil && h.RemoteICESupported() {
|
if h.ice != nil && h.RemoteICESupported() {
|
||||||
uFrag, pwd := h.ice.GetLocalUserCredentials()
|
uFrag, pwd := h.ice.GetLocalUserCredentials()
|
||||||
@@ -230,6 +235,30 @@ func (h *Handshaker) buildOfferAnswer() OfferAnswer {
|
|||||||
return answer
|
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) {
|
func (h *Handshaker) updateRemoteICEState(offer *OfferAnswer) {
|
||||||
hasICE := offer.hasICECredentials()
|
hasICE := offer.hasICECredentials()
|
||||||
prev := h.remoteICESupported.Swap(hasICE)
|
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,
|
UFrag: offerAnswer.IceCredentials.UFrag,
|
||||||
Pwd: offerAnswer.IceCredentials.Pwd,
|
Pwd: offerAnswer.IceCredentials.Pwd,
|
||||||
},
|
},
|
||||||
RosenpassPubKey: offerAnswer.RosenpassPubKey,
|
RosenpassPubKey: offerAnswer.RosenpassPubKey,
|
||||||
RosenpassAddr: offerAnswer.RosenpassAddr,
|
RosenpassPubKeyHash: offerAnswer.RosenpassPubKeyHash,
|
||||||
RelaySrvAddress: offerAnswer.RelaySrvAddress,
|
RosenpassPubKeyAck: offerAnswer.RosenpassPubKeyAck,
|
||||||
RelaySrvIP: offerAnswer.RelaySrvIP,
|
RosenpassAddr: offerAnswer.RosenpassAddr,
|
||||||
SessionID: sessionIDBytes,
|
RelaySrvAddress: offerAnswer.RelaySrvAddress,
|
||||||
|
RelaySrvIP: offerAnswer.RelaySrvIP,
|
||||||
|
SessionID: sessionIDBytes,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package rosenpass
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newCacheTestManager(spk []byte) *Manager {
|
||||||
|
return &Manager{
|
||||||
|
spk: spk,
|
||||||
|
remotePubKeys: make(map[string][]byte),
|
||||||
|
remoteHasLocalKey: make(map[string]bool),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveRemotePubKey(t *testing.T) {
|
||||||
|
m := newCacheTestManager([]byte{0x01, 0x02})
|
||||||
|
full := bytes.Repeat([]byte{0xAB}, 64)
|
||||||
|
|
||||||
|
// a received full key is cached and returned
|
||||||
|
require.Equal(t, full, m.ResolveRemotePubKey("peerA", full, nil))
|
||||||
|
|
||||||
|
// a later hash-only message resolves from the cache
|
||||||
|
require.Equal(t, full, m.ResolveRemotePubKey("peerA", nil, rawRosenpassKeyHash(full)))
|
||||||
|
|
||||||
|
// hash mismatch is a cache miss
|
||||||
|
require.Nil(t, m.ResolveRemotePubKey("peerA", nil, bytes.Repeat([]byte{0x01}, 32)))
|
||||||
|
|
||||||
|
// no key and no hash (remote without Rosenpass) resolves to nil
|
||||||
|
require.Nil(t, m.ResolveRemotePubKey("peerB", nil, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemotePubKeyAck(t *testing.T) {
|
||||||
|
m := newCacheTestManager([]byte{0x01})
|
||||||
|
|
||||||
|
// unknown peer -> no ack (signals "send me the full key")
|
||||||
|
require.Nil(t, m.RemotePubKeyAck("peerA"))
|
||||||
|
|
||||||
|
full := bytes.Repeat([]byte{0x09}, 48)
|
||||||
|
m.ResolveRemotePubKey("peerA", full, nil)
|
||||||
|
require.Equal(t, rawRosenpassKeyHash(full), m.RemotePubKeyAck("peerA"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetRemoteAckAndRemoteHasLocalKey(t *testing.T) {
|
||||||
|
m := newCacheTestManager(bytes.Repeat([]byte{0x07}, 100))
|
||||||
|
|
||||||
|
require.False(t, m.RemoteHasLocalKey("peerA"))
|
||||||
|
|
||||||
|
// an ack matching our own key hash marks the peer as holding our key
|
||||||
|
m.SetRemoteAck("peerA", m.LocalPubKeyHash())
|
||||||
|
require.True(t, m.RemoteHasLocalKey("peerA"))
|
||||||
|
|
||||||
|
// empty ack clears it
|
||||||
|
m.SetRemoteAck("peerA", nil)
|
||||||
|
require.False(t, m.RemoteHasLocalKey("peerA"))
|
||||||
|
|
||||||
|
// a non-matching ack does not count
|
||||||
|
m.SetRemoteAck("peerA", bytes.Repeat([]byte{0x01}, 32))
|
||||||
|
require.False(t, m.RemoteHasLocalKey("peerA"))
|
||||||
|
}
|
||||||
@@ -29,6 +29,11 @@ func hashRosenpassKey(key []byte) string {
|
|||||||
return hex.EncodeToString(hasher.Sum(nil))
|
return hex.EncodeToString(hasher.Sum(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func rawRosenpassKeyHash(key []byte) []byte {
|
||||||
|
sum := sha256.Sum256(key)
|
||||||
|
return sum[:]
|
||||||
|
}
|
||||||
|
|
||||||
// rpServer is the subset of rp.Server used by Manager. Defined as an interface
|
// rpServer is the subset of rp.Server used by Manager. Defined as an interface
|
||||||
// so tests can substitute a mock without spinning up a real UDP server.
|
// so tests can substitute a mock without spinning up a real UDP server.
|
||||||
type rpServer interface {
|
type rpServer interface {
|
||||||
@@ -51,6 +56,15 @@ type Manager struct {
|
|||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
port int
|
port int
|
||||||
wgIface PresharedKeySetter
|
wgIface PresharedKeySetter
|
||||||
|
|
||||||
|
// remotePubKeys caches remote peers' full Rosenpass public keys keyed by their
|
||||||
|
// WireGuard public key, so a peer that already sent us its (large) key over
|
||||||
|
// signalling need only send its hash on subsequent offers/answers. RAM only —
|
||||||
|
// never persisted (1000 peers x ~512KB would be ~512MB on disk).
|
||||||
|
remotePubKeys map[string][]byte
|
||||||
|
// remoteHasLocalKey tracks, per remote WireGuard key, whether that peer has
|
||||||
|
// acknowledged holding our current Rosenpass public key, letting us omit it.
|
||||||
|
remoteHasLocalKey map[string]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewManager creates a new Rosenpass manager. localWgKey is the local
|
// NewManager creates a new Rosenpass manager. localWgKey is the local
|
||||||
@@ -85,8 +99,10 @@ func NewManager(preSharedKey *wgtypes.Key, wgIfaceName string, localWgKey wgtype
|
|||||||
// nil receiver in addPeer -> m.rpWgHandler.AddPeer. generateConfig will
|
// nil receiver in addPeer -> m.rpWgHandler.AddPeer. generateConfig will
|
||||||
// replace it with a fresh handler on each Run() to clear stale peer
|
// replace it with a fresh handler on each Run() to clear stale peer
|
||||||
// state from previous engine sessions.
|
// state from previous engine sessions.
|
||||||
rpWgHandler: NewNetbirdHandler((*[32]byte)(preSharedKey), localWgKey),
|
rpWgHandler: NewNetbirdHandler((*[32]byte)(preSharedKey), localWgKey),
|
||||||
lock: sync.Mutex{},
|
lock: sync.Mutex{},
|
||||||
|
remotePubKeys: make(map[string][]byte),
|
||||||
|
remoteHasLocalKey: make(map[string]bool),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,6 +115,68 @@ func (m *Manager) GetAddress() *net.UDPAddr {
|
|||||||
return &net.UDPAddr{Port: m.port}
|
return &net.UDPAddr{Port: m.port}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LocalPubKeyHash returns the raw SHA256 of the local Rosenpass public key. It is
|
||||||
|
// advertised on every offer/answer so the remote peer can tell (via its cache)
|
||||||
|
// whether it already holds our full key.
|
||||||
|
func (m *Manager) LocalPubKeyHash() []byte {
|
||||||
|
return rawRosenpassKeyHash(m.spk)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemotePubKeyAck returns the SHA256 of the remote peer's cached public key, used
|
||||||
|
// as the acknowledgement we send back. Nil means we do not hold the peer's key,
|
||||||
|
// which signals the peer to include its full key next time.
|
||||||
|
func (m *Manager) RemotePubKeyAck(remoteWgKey string) []byte {
|
||||||
|
m.lock.Lock()
|
||||||
|
defer m.lock.Unlock()
|
||||||
|
|
||||||
|
key, ok := m.remotePubKeys[remoteWgKey]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return rawRosenpassKeyHash(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteHasLocalKey reports whether the remote peer acknowledged holding our
|
||||||
|
// current public key, so we may omit the full key from the next offer/answer.
|
||||||
|
func (m *Manager) RemoteHasLocalKey(remoteWgKey string) bool {
|
||||||
|
m.lock.Lock()
|
||||||
|
defer m.lock.Unlock()
|
||||||
|
|
||||||
|
return m.remoteHasLocalKey[remoteWgKey]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveRemotePubKey reconciles the Rosenpass key material from a received
|
||||||
|
// offer/answer: it caches a received full key, or — when only a hash was sent —
|
||||||
|
// returns the cached key matching that hash. It returns nil when the remote peer
|
||||||
|
// does not use Rosenpass (no key, no hash) or on a cache miss (hash sent but not
|
||||||
|
// held); a miss self-heals because our resulting empty ack makes the peer resend
|
||||||
|
// its full key.
|
||||||
|
func (m *Manager) ResolveRemotePubKey(remoteWgKey string, full, hash []byte) []byte {
|
||||||
|
m.lock.Lock()
|
||||||
|
defer m.lock.Unlock()
|
||||||
|
|
||||||
|
if len(full) > 0 {
|
||||||
|
m.remotePubKeys[remoteWgKey] = full
|
||||||
|
return full
|
||||||
|
}
|
||||||
|
if len(hash) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if cached, ok := m.remotePubKeys[remoteWgKey]; ok && bytes.Equal(rawRosenpassKeyHash(cached), hash) {
|
||||||
|
return cached
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRemoteAck records whether the remote peer's acknowledgement matches our
|
||||||
|
// current public key hash, i.e. whether it already holds our key.
|
||||||
|
func (m *Manager) SetRemoteAck(remoteWgKey string, ack []byte) {
|
||||||
|
m.lock.Lock()
|
||||||
|
defer m.lock.Unlock()
|
||||||
|
|
||||||
|
m.remoteHasLocalKey[remoteWgKey] = len(ack) > 0 && bytes.Equal(ack, rawRosenpassKeyHash(m.spk))
|
||||||
|
}
|
||||||
|
|
||||||
// addPeer adds a new peer to the Rosenpass server
|
// addPeer adds a new peer to the Rosenpass server
|
||||||
func (m *Manager) addPeer(rosenpassPubKey []byte, rosenpassAddr string, wireGuardIP string, wireGuardPubKey string) error {
|
func (m *Manager) addPeer(rosenpassPubKey []byte, rosenpassAddr string, wireGuardIP string, wireGuardPubKey string) error {
|
||||||
// Defense in depth against issue #4341 (Android crash): if Run() has not
|
// Defense in depth against issue #4341 (Android crash): if Run() has not
|
||||||
|
|||||||
@@ -51,10 +51,17 @@ type CredentialPayload struct {
|
|||||||
WgListenPort int
|
WgListenPort int
|
||||||
Credential *Credential
|
Credential *Credential
|
||||||
RosenpassPubKey []byte
|
RosenpassPubKey []byte
|
||||||
RosenpassAddr string
|
// RosenpassPubKeyHash is the SHA256 of the sender's own RosenpassPubKey (empty
|
||||||
RelaySrvAddress string
|
// when Rosenpass is disabled). RosenpassPubKey may be omitted when the peer has
|
||||||
RelaySrvIP netip.Addr
|
// already acknowledged this hash. See RosenpassConfig in the proto.
|
||||||
SessionID []byte
|
RosenpassPubKeyHash []byte
|
||||||
|
// RosenpassPubKeyAck is the SHA256 of the remote peer's key the sender holds
|
||||||
|
// cached; empty means "send me the full key".
|
||||||
|
RosenpassPubKeyAck []byte
|
||||||
|
RosenpassAddr string
|
||||||
|
RelaySrvAddress string
|
||||||
|
RelaySrvIP netip.Addr
|
||||||
|
SessionID []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnMarshalCredential parses the credentials from the message and returns a Credential instance
|
// UnMarshalCredential parses the credentials from the message and returns a Credential instance
|
||||||
@@ -78,8 +85,10 @@ func MarshalCredential(myKey wgtypes.Key, remoteKey string, p CredentialPayload)
|
|||||||
WgListenPort: uint32(p.WgListenPort),
|
WgListenPort: uint32(p.WgListenPort),
|
||||||
NetBirdVersion: version.NetbirdVersion(),
|
NetBirdVersion: version.NetbirdVersion(),
|
||||||
RosenpassConfig: &proto.RosenpassConfig{
|
RosenpassConfig: &proto.RosenpassConfig{
|
||||||
RosenpassPubKey: p.RosenpassPubKey,
|
RosenpassPubKey: p.RosenpassPubKey,
|
||||||
RosenpassServerAddr: p.RosenpassAddr,
|
RosenpassServerAddr: p.RosenpassAddr,
|
||||||
|
RosenpassPubKeyHash: p.RosenpassPubKeyHash,
|
||||||
|
AcknowledgedRosenpassPubKeyHash: p.RosenpassPubKeyAck,
|
||||||
},
|
},
|
||||||
SessionId: p.SessionID,
|
SessionId: p.SessionID,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.26.0
|
// protoc-gen-go v1.26.0
|
||||||
// protoc v3.21.12
|
// protoc v6.33.1
|
||||||
// source: signalexchange.proto
|
// source: signalexchange.proto
|
||||||
|
|
||||||
package proto
|
package proto
|
||||||
@@ -399,6 +399,17 @@ type RosenpassConfig struct {
|
|||||||
RosenpassPubKey []byte `protobuf:"bytes,1,opt,name=rosenpassPubKey,proto3" json:"rosenpassPubKey,omitempty"`
|
RosenpassPubKey []byte `protobuf:"bytes,1,opt,name=rosenpassPubKey,proto3" json:"rosenpassPubKey,omitempty"`
|
||||||
// rosenpassServerAddr is an IP:port of the rosenpass service
|
// rosenpassServerAddr is an IP:port of the rosenpass service
|
||||||
RosenpassServerAddr string `protobuf:"bytes,2,opt,name=rosenpassServerAddr,proto3" json:"rosenpassServerAddr,omitempty"`
|
RosenpassServerAddr string `protobuf:"bytes,2,opt,name=rosenpassServerAddr,proto3" json:"rosenpassServerAddr,omitempty"`
|
||||||
|
// rosenpassPubKeyHash is the SHA256 of the sender's own rosenpassPubKey. It is
|
||||||
|
// always set when Rosenpass is enabled and lets the receiver detect (via a
|
||||||
|
// per-peer cache) whether it already holds the sender's full public key,
|
||||||
|
// avoiding re-sending the large key on every offer/answer.
|
||||||
|
RosenpassPubKeyHash []byte `protobuf:"bytes,3,opt,name=rosenpassPubKeyHash,proto3" json:"rosenpassPubKeyHash,omitempty"`
|
||||||
|
// acknowledgedRosenpassPubKeyHash is the SHA256 of the remote peer's rosenpassPubKey
|
||||||
|
// that the sender currently holds cached. When it matches the receiver's own key hash
|
||||||
|
// the receiver may omit its full rosenpassPubKey from the message. Empty means the
|
||||||
|
// sender does not have the remote key and needs it sent in full. Absent from peers
|
||||||
|
// that predate this field, which keeps them receiving the full key as before.
|
||||||
|
AcknowledgedRosenpassPubKeyHash []byte `protobuf:"bytes,4,opt,name=acknowledgedRosenpassPubKeyHash,proto3" json:"acknowledgedRosenpassPubKeyHash,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RosenpassConfig) Reset() {
|
func (x *RosenpassConfig) Reset() {
|
||||||
@@ -447,6 +458,20 @@ func (x *RosenpassConfig) GetRosenpassServerAddr() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *RosenpassConfig) GetRosenpassPubKeyHash() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.RosenpassPubKeyHash
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RosenpassConfig) GetAcknowledgedRosenpassPubKeyHash() []byte {
|
||||||
|
if x != nil {
|
||||||
|
return x.AcknowledgedRosenpassPubKeyHash
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var File_signalexchange_proto protoreflect.FileDescriptor
|
var File_signalexchange_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
var file_signalexchange_proto_rawDesc = []byte{
|
var file_signalexchange_proto_rawDesc = []byte{
|
||||||
@@ -506,27 +531,35 @@ var file_signalexchange_proto_rawDesc = []byte{
|
|||||||
0x65, 0x72, 0x49, 0x50, 0x4a, 0x04, 0x08, 0x09, 0x10, 0x0a, 0x22, 0x2e, 0x0a, 0x04, 0x4d, 0x6f,
|
0x65, 0x72, 0x49, 0x50, 0x4a, 0x04, 0x08, 0x09, 0x10, 0x0a, 0x22, 0x2e, 0x0a, 0x04, 0x4d, 0x6f,
|
||||||
0x64, 0x65, 0x12, 0x1b, 0x0a, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01,
|
0x64, 0x65, 0x12, 0x1b, 0x0a, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01,
|
||||||
0x28, 0x08, 0x48, 0x00, 0x52, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x88, 0x01, 0x01, 0x42,
|
0x28, 0x08, 0x48, 0x00, 0x52, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x88, 0x01, 0x01, 0x42,
|
||||||
0x09, 0x0a, 0x07, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x22, 0x6d, 0x0a, 0x0f, 0x52, 0x6f,
|
0x09, 0x0a, 0x07, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x22, 0xe9, 0x01, 0x0a, 0x0f, 0x52,
|
||||||
0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x28, 0x0a,
|
0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x28,
|
||||||
0x0f, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79,
|
0x0a, 0x0f, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x50, 0x75, 0x62, 0x4b, 0x65,
|
||||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73,
|
0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61,
|
||||||
0x73, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x13, 0x72, 0x6f, 0x73, 0x65, 0x6e,
|
0x73, 0x73, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x13, 0x72, 0x6f, 0x73, 0x65,
|
||||||
0x70, 0x61, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x18, 0x02,
|
0x6e, 0x70, 0x61, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x18,
|
||||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x53,
|
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73,
|
||||||
0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x32, 0xb9, 0x01, 0x0a, 0x0e, 0x53, 0x69,
|
0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x12, 0x30, 0x0a, 0x13, 0x72, 0x6f,
|
||||||
0x67, 0x6e, 0x61, 0x6c, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x4c, 0x0a, 0x04,
|
0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x48, 0x61, 0x73,
|
||||||
0x53, 0x65, 0x6e, 0x64, 0x12, 0x20, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63,
|
0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x13, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61,
|
||||||
0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d,
|
0x73, 0x73, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x48, 0x61, 0x73, 0x68, 0x12, 0x48, 0x0a, 0x1f,
|
||||||
0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x20, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65,
|
0x61, 0x63, 0x6b, 0x6e, 0x6f, 0x77, 0x6c, 0x65, 0x64, 0x67, 0x65, 0x64, 0x52, 0x6f, 0x73, 0x65,
|
||||||
0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65,
|
0x6e, 0x70, 0x61, 0x73, 0x73, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x48, 0x61, 0x73, 0x68, 0x18,
|
||||||
0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x59, 0x0a, 0x0d, 0x43, 0x6f,
|
0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x1f, 0x61, 0x63, 0x6b, 0x6e, 0x6f, 0x77, 0x6c, 0x65, 0x64,
|
||||||
0x6e, 0x6e, 0x65, 0x63, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x20, 0x2e, 0x73, 0x69,
|
0x67, 0x65, 0x64, 0x52, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x50, 0x75, 0x62, 0x4b,
|
||||||
0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x45, 0x6e, 0x63,
|
0x65, 0x79, 0x48, 0x61, 0x73, 0x68, 0x32, 0xb9, 0x01, 0x0a, 0x0e, 0x53, 0x69, 0x67, 0x6e, 0x61,
|
||||||
0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x20, 0x2e,
|
0x6c, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x4c, 0x0a, 0x04, 0x53, 0x65, 0x6e,
|
||||||
0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x45,
|
0x64, 0x12, 0x20, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e,
|
||||||
0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22,
|
0x67, 0x65, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73,
|
||||||
0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
0x61, 0x67, 0x65, 0x1a, 0x20, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63, 0x68,
|
||||||
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
0x61, 0x6e, 0x67, 0x65, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65,
|
||||||
|
0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x59, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x6e, 0x65,
|
||||||
|
0x63, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x20, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61,
|
||||||
|
0x6c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70,
|
||||||
|
0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x20, 0x2e, 0x73, 0x69, 0x67,
|
||||||
|
0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x45, 0x6e, 0x63, 0x72,
|
||||||
|
0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x28, 0x01,
|
||||||
|
0x30, 0x01, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72,
|
||||||
|
0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -86,4 +86,15 @@ message RosenpassConfig {
|
|||||||
bytes rosenpassPubKey = 1;
|
bytes rosenpassPubKey = 1;
|
||||||
// rosenpassServerAddr is an IP:port of the rosenpass service
|
// rosenpassServerAddr is an IP:port of the rosenpass service
|
||||||
string rosenpassServerAddr = 2;
|
string rosenpassServerAddr = 2;
|
||||||
|
// rosenpassPubKeyHash is the SHA256 of the sender's own rosenpassPubKey. It is
|
||||||
|
// always set when Rosenpass is enabled and lets the receiver detect (via a
|
||||||
|
// per-peer cache) whether it already holds the sender's full public key,
|
||||||
|
// avoiding re-sending the large key on every offer/answer.
|
||||||
|
bytes rosenpassPubKeyHash = 3;
|
||||||
|
// acknowledgedRosenpassPubKeyHash is the SHA256 of the remote peer's rosenpassPubKey
|
||||||
|
// that the sender currently holds cached. When it matches the receiver's own key hash
|
||||||
|
// the receiver may omit its full rosenpassPubKey from the message. Empty means the
|
||||||
|
// sender does not have the remote key and needs it sent in full. Absent from peers
|
||||||
|
// that predate this field, which keeps them receiving the full key as before.
|
||||||
|
bytes acknowledgedRosenpassPubKeyHash = 4;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user