Commit c1bac941 authored by Petar Radovic's avatar Petar Radovic Committed by GitHub

Handshake with observable underlay (#257)

* handshake with observable underlay
parent ebb776f4
...@@ -35,9 +35,12 @@ type addressJSON struct { ...@@ -35,9 +35,12 @@ type addressJSON struct {
} }
func NewAddress(signer crypto.Signer, underlay ma.Multiaddr, overlay swarm.Address, networkID uint64) (*Address, error) { func NewAddress(signer crypto.Signer, underlay ma.Multiaddr, overlay swarm.Address, networkID uint64) (*Address, error) {
networkIDBytes := make([]byte, 8) underlayBinary, err := underlay.MarshalBinary()
binary.BigEndian.PutUint64(networkIDBytes, networkID) if err != nil {
signature, err := signer.Sign(append(underlay.Bytes(), networkIDBytes...)) return nil, err
}
signature, err := signer.Sign(generateSignData(underlayBinary, overlay.Bytes(), networkID))
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -50,9 +53,7 @@ func NewAddress(signer crypto.Signer, underlay ma.Multiaddr, overlay swarm.Addre ...@@ -50,9 +53,7 @@ func NewAddress(signer crypto.Signer, underlay ma.Multiaddr, overlay swarm.Addre
} }
func ParseAddress(underlay, overlay, signature []byte, networkID uint64) (*Address, error) { func ParseAddress(underlay, overlay, signature []byte, networkID uint64) (*Address, error) {
networkIDBytes := make([]byte, 8) recoveredPK, err := crypto.Recover(signature, generateSignData(underlay, overlay, networkID))
binary.BigEndian.PutUint64(networkIDBytes, networkID)
recoveredPK, err := crypto.Recover(signature, append(underlay, networkIDBytes...))
if err != nil { if err != nil {
return nil, ErrInvalidAddress return nil, ErrInvalidAddress
} }
...@@ -74,6 +75,13 @@ func ParseAddress(underlay, overlay, signature []byte, networkID uint64) (*Addre ...@@ -74,6 +75,13 @@ func ParseAddress(underlay, overlay, signature []byte, networkID uint64) (*Addre
}, nil }, nil
} }
func generateSignData(underlay, overlay []byte, networkID uint64) []byte {
networkIDBytes := make([]byte, 8)
binary.BigEndian.PutUint64(networkIDBytes, networkID)
signData := append(underlay, overlay...)
return append(signData, networkIDBytes...)
}
func (a *Address) Equal(b *Address) bool { func (a *Address) Equal(b *Address) bool {
return a.Overlay.Equal(b.Overlay) && a.Underlay.Equal(b.Underlay) && bytes.Equal(a.Signature, b.Signature) return a.Overlay.Equal(b.Overlay) && a.Underlay.Equal(b.Underlay) && bytes.Equal(a.Signature, b.Signature)
} }
......
...@@ -270,9 +270,7 @@ func TestConnectRepeatHandshake(t *testing.T) { ...@@ -270,9 +270,7 @@ func TestConnectRepeatHandshake(t *testing.T) {
defer cancel() defer cancel()
s1, overlay1 := newService(t, 1, libp2p.Options{}) s1, overlay1 := newService(t, 1, libp2p.Options{})
s2, overlay2 := newService(t, 1, libp2p.Options{}) s2, overlay2 := newService(t, 1, libp2p.Options{})
addr := serviceUnderlayAddress(t, s1) addr := serviceUnderlayAddress(t, s1)
_, err := s2.Connect(ctx, addr) _, err := s2.Connect(ctx, addr)
...@@ -293,7 +291,7 @@ func TestConnectRepeatHandshake(t *testing.T) { ...@@ -293,7 +291,7 @@ func TestConnectRepeatHandshake(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if _, err := s2.HandshakeService().Handshake(libp2p.NewStream(stream)); err == nil { if _, err := s2.HandshakeService().Handshake(libp2p.NewStream(stream), info.Addrs[0], info.ID); err == nil {
t.Fatalf("expected stream error") t.Fatalf("expected stream error")
} }
......
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
package handshake package handshake
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"sync" "sync"
...@@ -38,20 +37,17 @@ var ( ...@@ -38,20 +37,17 @@ var (
// ErrHandshakeDuplicate is returned if the handshake response has been received by an already processed peer. // ErrHandshakeDuplicate is returned if the handshake response has been received by an already processed peer.
ErrHandshakeDuplicate = errors.New("duplicate handshake") ErrHandshakeDuplicate = errors.New("duplicate handshake")
// ErrInvalidBzzAddress is returned if peer info was received with invalid bzz address // ErrInvalidAck is returned if data in received in ack is not valid (invalid signature for example).
ErrInvalidBzzAddress = errors.New("invalid bzz address")
// ErrInvalidAck is returned if ack does not match the syn provided
ErrInvalidAck = errors.New("invalid ack") ErrInvalidAck = errors.New("invalid ack")
)
// PeerFinder has the information if the peer already exists in swarm. // ErrInvalidSyn is returned if observable address in ack is not a valid..
type PeerFinder interface { ErrInvalidSyn = errors.New("invalid syn")
Exists(overlay swarm.Address) (found bool) )
}
type Service struct { type Service struct {
bzzAddress bzz.Address signer crypto.Signer
overlay swarm.Address
lightNode bool
networkID uint64 networkID uint64
receivedHandshakes map[libp2ppeer.ID]struct{} receivedHandshakes map[libp2ppeer.ID]struct{}
receivedHandshakesMu sync.Mutex receivedHandshakesMu sync.Mutex
...@@ -60,30 +56,32 @@ type Service struct { ...@@ -60,30 +56,32 @@ type Service struct {
network.Notifiee // handshake service can be the receiver for network.Notify network.Notifiee // handshake service can be the receiver for network.Notify
} }
func New(overlay swarm.Address, underlay ma.Multiaddr, signer crypto.Signer, networkID uint64, logger logging.Logger) (*Service, error) { func New(overlay swarm.Address, signer crypto.Signer, networkID uint64, lighNode bool, logger logging.Logger) (*Service, error) {
bzzAddress, err := bzz.NewAddress(signer, underlay, overlay, networkID)
if err != nil {
return nil, err
}
return &Service{ return &Service{
bzzAddress: *bzzAddress, signer: signer,
overlay: overlay,
networkID: networkID, networkID: networkID,
lightNode: lighNode,
receivedHandshakes: make(map[libp2ppeer.ID]struct{}), receivedHandshakes: make(map[libp2ppeer.ID]struct{}),
logger: logger, logger: logger,
Notifiee: new(network.NoopNotifiee), Notifiee: new(network.NoopNotifiee),
}, nil }, nil
} }
func (s *Service) Handshake(stream p2p.Stream) (i *Info, err error) { func (s *Service) Handshake(stream p2p.Stream, peerMultiaddr ma.Multiaddr, peerID libp2ppeer.ID) (i *Info, err error) {
w, r := protobuf.NewWriterAndReader(stream) w, r := protobuf.NewWriterAndReader(stream)
fullRemoteMA, err := buildFullMA(peerMultiaddr, peerID)
if err != nil {
return nil, err
}
fullRemoteMABytes, err := fullRemoteMA.MarshalBinary()
if err != nil {
return nil, err
}
if err := w.WriteMsgWithTimeout(messageTimeout, &pb.Syn{ if err := w.WriteMsgWithTimeout(messageTimeout, &pb.Syn{
BzzAddress: &pb.BzzAddress{ ObservedUnderlay: fullRemoteMABytes,
Underlay: s.bzzAddress.Underlay.Bytes(),
Signature: s.bzzAddress.Signature,
Overlay: s.bzzAddress.Overlay.Bytes(),
},
NetworkID: s.networkID,
}); err != nil { }); err != nil {
return nil, fmt.Errorf("write syn message: %w", err) return nil, fmt.Errorf("write syn message: %w", err)
} }
...@@ -93,67 +91,82 @@ func (s *Service) Handshake(stream p2p.Stream) (i *Info, err error) { ...@@ -93,67 +91,82 @@ func (s *Service) Handshake(stream p2p.Stream) (i *Info, err error) {
return nil, fmt.Errorf("read synack message: %w", err) return nil, fmt.Errorf("read synack message: %w", err)
} }
if err := s.checkAck(resp.Ack); err != nil { remoteBzzAddress, err := s.parseCheckAck(resp.Ack, fullRemoteMABytes)
if err != nil {
return nil, err return nil, err
} }
if resp.Syn.NetworkID != s.networkID { addr, err := ma.NewMultiaddrBytes(resp.Syn.ObservedUnderlay)
return nil, ErrNetworkIDIncompatible if err != nil {
return nil, ErrInvalidSyn
} }
bzzAddress, err := bzz.ParseAddress(resp.Syn.BzzAddress.Underlay, resp.Syn.BzzAddress.Overlay, resp.Syn.BzzAddress.Signature, resp.Syn.NetworkID) bzzAddress, err := bzz.NewAddress(s.signer, addr, s.overlay, s.networkID)
if err != nil { if err != nil {
return nil, ErrInvalidBzzAddress return nil, err
} }
if err := w.WriteMsgWithTimeout(messageTimeout, &pb.Ack{ if err := w.WriteMsgWithTimeout(messageTimeout, &pb.Ack{
BzzAddress: resp.Syn.BzzAddress, Overlay: bzzAddress.Overlay.Bytes(),
Signature: bzzAddress.Signature,
NetworkID: s.networkID,
Light: s.lightNode,
}); err != nil { }); err != nil {
return nil, fmt.Errorf("write ack message: %w", err) return nil, fmt.Errorf("write ack message: %w", err)
} }
s.logger.Tracef("handshake finished for peer %s", swarm.NewAddress(resp.Syn.BzzAddress.Overlay).String()) s.logger.Tracef("handshake finished for peer %s", remoteBzzAddress.Overlay.String())
return &Info{ return &Info{
BzzAddress: bzzAddress, BzzAddress: remoteBzzAddress,
Light: resp.Syn.Light, Light: resp.Ack.Light,
}, nil }, nil
} }
func (s *Service) Handle(stream p2p.Stream, peerID libp2ppeer.ID) (i *Info, err error) { func (s *Service) Handle(stream p2p.Stream, remoteMultiaddr ma.Multiaddr, remotePeerID libp2ppeer.ID) (i *Info, err error) {
s.receivedHandshakesMu.Lock() s.receivedHandshakesMu.Lock()
if _, exists := s.receivedHandshakes[peerID]; exists { if _, exists := s.receivedHandshakes[remotePeerID]; exists {
s.receivedHandshakesMu.Unlock() s.receivedHandshakesMu.Unlock()
return nil, ErrHandshakeDuplicate return nil, ErrHandshakeDuplicate
} }
s.receivedHandshakes[peerID] = struct{}{} s.receivedHandshakes[remotePeerID] = struct{}{}
s.receivedHandshakesMu.Unlock() s.receivedHandshakesMu.Unlock()
w, r := protobuf.NewWriterAndReader(stream) w, r := protobuf.NewWriterAndReader(stream)
fullRemoteMA, err := buildFullMA(remoteMultiaddr, remotePeerID)
if err != nil {
return nil, err
}
var req pb.Syn fullRemoteMABytes, err := fullRemoteMA.MarshalBinary()
if err := r.ReadMsgWithTimeout(messageTimeout, &req); err != nil { if err != nil {
return nil, err
}
var syn pb.Syn
if err := r.ReadMsgWithTimeout(messageTimeout, &syn); err != nil {
return nil, fmt.Errorf("read syn message: %w", err) return nil, fmt.Errorf("read syn message: %w", err)
} }
if req.NetworkID != s.networkID { addr, err := ma.NewMultiaddrBytes(syn.ObservedUnderlay)
return nil, ErrNetworkIDIncompatible if err != nil {
return nil, ErrInvalidSyn
} }
bzzAddress, err := bzz.ParseAddress(req.BzzAddress.Underlay, req.BzzAddress.Overlay, req.BzzAddress.Signature, req.NetworkID) bzzAddress, err := bzz.NewAddress(s.signer, addr, s.overlay, s.networkID)
if err != nil { if err != nil {
return nil, ErrInvalidBzzAddress return nil, err
} }
if err := w.WriteMsgWithTimeout(messageTimeout, &pb.SynAck{ if err := w.WriteMsgWithTimeout(messageTimeout, &pb.SynAck{
Syn: &pb.Syn{ Syn: &pb.Syn{
BzzAddress: &pb.BzzAddress{ ObservedUnderlay: fullRemoteMABytes,
Underlay: s.bzzAddress.Underlay.Bytes(), },
Signature: s.bzzAddress.Signature, Ack: &pb.Ack{
Overlay: s.bzzAddress.Overlay.Bytes(), Overlay: bzzAddress.Overlay.Bytes(),
}, Signature: bzzAddress.Signature,
NetworkID: s.networkID, NetworkID: s.networkID,
Light: s.lightNode,
}, },
Ack: &pb.Ack{BzzAddress: req.BzzAddress},
}); err != nil { }); err != nil {
return nil, fmt.Errorf("write synack message: %w", err) return nil, fmt.Errorf("write synack message: %w", err)
} }
...@@ -163,14 +176,15 @@ func (s *Service) Handle(stream p2p.Stream, peerID libp2ppeer.ID) (i *Info, err ...@@ -163,14 +176,15 @@ func (s *Service) Handle(stream p2p.Stream, peerID libp2ppeer.ID) (i *Info, err
return nil, fmt.Errorf("read ack message: %w", err) return nil, fmt.Errorf("read ack message: %w", err)
} }
if err := s.checkAck(&ack); err != nil { remoteBzzAddress, err := s.parseCheckAck(&ack, fullRemoteMABytes)
if err != nil {
return nil, err return nil, err
} }
s.logger.Tracef("handshake finished for peer %s", swarm.NewAddress(req.BzzAddress.Overlay).String()) s.logger.Tracef("handshake finished for peer %s", remoteBzzAddress.Overlay.String())
return &Info{ return &Info{
BzzAddress: bzzAddress, BzzAddress: remoteBzzAddress,
Light: req.Light, Light: ack.Light,
}, nil }, nil
} }
...@@ -180,14 +194,21 @@ func (s *Service) Disconnected(_ network.Network, c network.Conn) { ...@@ -180,14 +194,21 @@ func (s *Service) Disconnected(_ network.Network, c network.Conn) {
delete(s.receivedHandshakes, c.RemotePeer()) delete(s.receivedHandshakes, c.RemotePeer())
} }
func (s *Service) checkAck(ack *pb.Ack) error { func buildFullMA(addr ma.Multiaddr, peerID libp2ppeer.ID) (ma.Multiaddr, error) {
if !bytes.Equal(ack.BzzAddress.Overlay, s.bzzAddress.Overlay.Bytes()) || return ma.NewMultiaddr(fmt.Sprintf("%s/p2p/%s", addr.String(), peerID.Pretty()))
!bytes.Equal(ack.BzzAddress.Underlay, s.bzzAddress.Underlay.Bytes()) || }
!bytes.Equal(ack.BzzAddress.Signature, s.bzzAddress.Signature) {
return ErrInvalidAck func (s *Service) parseCheckAck(ack *pb.Ack, remoteMA []byte) (*bzz.Address, error) {
if ack.NetworkID != s.networkID {
return nil, ErrNetworkIDIncompatible
}
bzzAddress, err := bzz.ParseAddress(remoteMA, ack.Overlay, ack.Signature, s.networkID)
if err != nil {
return nil, ErrInvalidAck
} }
return nil return bzzAddress, nil
} }
type Info struct { type Info struct {
......
...@@ -9,22 +9,17 @@ package handshake; ...@@ -9,22 +9,17 @@ package handshake;
option go_package = "pb"; option go_package = "pb";
message Syn { message Syn {
BzzAddress BzzAddress = 1; bytes ObservedUnderlay = 1;
uint64 NetworkID = 2;
bool Light = 3;
} }
message Ack { message Ack {
BzzAddress BzzAddress = 1; bytes Overlay = 1;
bytes Signature = 2;
uint64 NetworkID = 3;
bool Light = 4;
} }
message SynAck { message SynAck {
Syn Syn = 1; Syn Syn = 1;
Ack Ack = 2; Ack Ack = 2;
} }
\ No newline at end of file
message BzzAddress {
bytes Underlay = 1;
bytes Signature = 2;
bytes Overlay = 3;
}
...@@ -61,6 +61,7 @@ type Options struct { ...@@ -61,6 +61,7 @@ type Options struct {
PrivateKey *ecdsa.PrivateKey PrivateKey *ecdsa.PrivateKey
DisableWS bool DisableWS bool
DisableQUIC bool DisableQUIC bool
LightNode bool
Addressbook addressbook.Putter Addressbook addressbook.Putter
Logger logging.Logger Logger logging.Logger
Tracer *tracing.Tracer Tracer *tracing.Tracer
...@@ -156,13 +157,7 @@ func New(ctx context.Context, signer beecrypto.Signer, networkID uint64, overlay ...@@ -156,13 +157,7 @@ func New(ctx context.Context, signer beecrypto.Signer, networkID uint64, overlay
return nil, fmt.Errorf("autonat: %w", err) return nil, fmt.Errorf("autonat: %w", err)
} }
// todo: handle different underlays handshakeService, err := handshake.New(overlay, signer, networkID, o.LightNode, o.Logger)
underlay, err := buildUnderlayAddress(h.Addrs()[1], h.ID())
if err != nil {
return nil, fmt.Errorf("build host multiaddress: %w", err)
}
handshakeService, err := handshake.New(overlay, underlay, signer, networkID, o.Logger)
if err != nil { if err != nil {
return nil, fmt.Errorf("handshake service: %w", err) return nil, fmt.Errorf("handshake service: %w", err)
} }
...@@ -192,7 +187,7 @@ func New(ctx context.Context, signer beecrypto.Signer, networkID uint64, overlay ...@@ -192,7 +187,7 @@ func New(ctx context.Context, signer beecrypto.Signer, networkID uint64, overlay
// handshake // handshake
s.host.SetStreamHandlerMatch(id, matcher, func(stream network.Stream) { s.host.SetStreamHandlerMatch(id, matcher, func(stream network.Stream) {
peerID := stream.Conn().RemotePeer() peerID := stream.Conn().RemotePeer()
i, err := s.handshakeService.Handle(NewStream(stream), peerID) i, err := s.handshakeService.Handle(NewStream(stream), stream.Conn().RemoteMultiaddr(), peerID)
if err != nil { if err != nil {
s.logger.Debugf("handshake: handle %s: %v", peerID, err) s.logger.Debugf("handshake: handle %s: %v", peerID, err)
s.logger.Errorf("unable to handshake with peer %v", peerID) s.logger.Errorf("unable to handshake with peer %v", peerID)
...@@ -334,7 +329,7 @@ func (s *Service) Connect(ctx context.Context, addr ma.Multiaddr) (address *bzz. ...@@ -334,7 +329,7 @@ func (s *Service) Connect(ctx context.Context, addr ma.Multiaddr) (address *bzz.
return nil, err return nil, err
} }
i, err := s.handshakeService.Handshake(NewStream(stream)) i, err := s.handshakeService.Handshake(NewStream(stream), stream.Conn().RemoteMultiaddr(), stream.Conn().RemotePeer())
if err != nil { if err != nil {
_ = s.disconnect(info.ID) _ = s.disconnect(info.ID)
return nil, fmt.Errorf("handshake: %w", err) return nil, fmt.Errorf("handshake: %w", err)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment