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 {
}
func NewAddress(signer crypto.Signer, underlay ma.Multiaddr, overlay swarm.Address, networkID uint64) (*Address, error) {
networkIDBytes := make([]byte, 8)
binary.BigEndian.PutUint64(networkIDBytes, networkID)
signature, err := signer.Sign(append(underlay.Bytes(), networkIDBytes...))
underlayBinary, err := underlay.MarshalBinary()
if err != nil {
return nil, err
}
signature, err := signer.Sign(generateSignData(underlayBinary, overlay.Bytes(), networkID))
if err != nil {
return nil, err
}
......@@ -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) {
networkIDBytes := make([]byte, 8)
binary.BigEndian.PutUint64(networkIDBytes, networkID)
recoveredPK, err := crypto.Recover(signature, append(underlay, networkIDBytes...))
recoveredPK, err := crypto.Recover(signature, generateSignData(underlay, overlay, networkID))
if err != nil {
return nil, ErrInvalidAddress
}
......@@ -74,6 +75,13 @@ func ParseAddress(underlay, overlay, signature []byte, networkID uint64) (*Addre
}, 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 {
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) {
defer cancel()
s1, overlay1 := newService(t, 1, libp2p.Options{})
s2, overlay2 := newService(t, 1, libp2p.Options{})
addr := serviceUnderlayAddress(t, s1)
_, err := s2.Connect(ctx, addr)
......@@ -293,7 +291,7 @@ func TestConnectRepeatHandshake(t *testing.T) {
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")
}
......
......@@ -5,7 +5,6 @@
package handshake
import (
"bytes"
"errors"
"fmt"
"sync"
......@@ -38,20 +37,17 @@ var (
// ErrHandshakeDuplicate is returned if the handshake response has been received by an already processed peer.
ErrHandshakeDuplicate = errors.New("duplicate handshake")
// ErrInvalidBzzAddress is returned if peer info was received with invalid bzz address
ErrInvalidBzzAddress = errors.New("invalid bzz address")
// ErrInvalidAck is returned if ack does not match the syn provided
// ErrInvalidAck is returned if data in received in ack is not valid (invalid signature for example).
ErrInvalidAck = errors.New("invalid ack")
)
// PeerFinder has the information if the peer already exists in swarm.
type PeerFinder interface {
Exists(overlay swarm.Address) (found bool)
}
// ErrInvalidSyn is returned if observable address in ack is not a valid..
ErrInvalidSyn = errors.New("invalid syn")
)
type Service struct {
bzzAddress bzz.Address
signer crypto.Signer
overlay swarm.Address
lightNode bool
networkID uint64
receivedHandshakes map[libp2ppeer.ID]struct{}
receivedHandshakesMu sync.Mutex
......@@ -60,30 +56,32 @@ type Service struct {
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) {
bzzAddress, err := bzz.NewAddress(signer, underlay, overlay, networkID)
if err != nil {
return nil, err
}
func New(overlay swarm.Address, signer crypto.Signer, networkID uint64, lighNode bool, logger logging.Logger) (*Service, error) {
return &Service{
bzzAddress: *bzzAddress,
signer: signer,
overlay: overlay,
networkID: networkID,
lightNode: lighNode,
receivedHandshakes: make(map[libp2ppeer.ID]struct{}),
logger: logger,
Notifiee: new(network.NoopNotifiee),
}, 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)
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{
BzzAddress: &pb.BzzAddress{
Underlay: s.bzzAddress.Underlay.Bytes(),
Signature: s.bzzAddress.Signature,
Overlay: s.bzzAddress.Overlay.Bytes(),
},
NetworkID: s.networkID,
ObservedUnderlay: fullRemoteMABytes,
}); err != nil {
return nil, fmt.Errorf("write syn message: %w", err)
}
......@@ -93,67 +91,82 @@ func (s *Service) Handshake(stream p2p.Stream) (i *Info, err error) {
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
}
if resp.Syn.NetworkID != s.networkID {
return nil, ErrNetworkIDIncompatible
addr, err := ma.NewMultiaddrBytes(resp.Syn.ObservedUnderlay)
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 {
return nil, ErrInvalidBzzAddress
return nil, err
}
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 {
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{
BzzAddress: bzzAddress,
Light: resp.Syn.Light,
BzzAddress: remoteBzzAddress,
Light: resp.Ack.Light,
}, 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()
if _, exists := s.receivedHandshakes[peerID]; exists {
if _, exists := s.receivedHandshakes[remotePeerID]; exists {
s.receivedHandshakesMu.Unlock()
return nil, ErrHandshakeDuplicate
}
s.receivedHandshakes[peerID] = struct{}{}
s.receivedHandshakes[remotePeerID] = struct{}{}
s.receivedHandshakesMu.Unlock()
w, r := protobuf.NewWriterAndReader(stream)
fullRemoteMA, err := buildFullMA(remoteMultiaddr, remotePeerID)
if err != nil {
return nil, err
}
fullRemoteMABytes, err := fullRemoteMA.MarshalBinary()
if err != nil {
return nil, err
}
var req pb.Syn
if err := r.ReadMsgWithTimeout(messageTimeout, &req); err != nil {
var syn pb.Syn
if err := r.ReadMsgWithTimeout(messageTimeout, &syn); err != nil {
return nil, fmt.Errorf("read syn message: %w", err)
}
if req.NetworkID != s.networkID {
return nil, ErrNetworkIDIncompatible
addr, err := ma.NewMultiaddrBytes(syn.ObservedUnderlay)
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 {
return nil, ErrInvalidBzzAddress
return nil, err
}
if err := w.WriteMsgWithTimeout(messageTimeout, &pb.SynAck{
Syn: &pb.Syn{
BzzAddress: &pb.BzzAddress{
Underlay: s.bzzAddress.Underlay.Bytes(),
Signature: s.bzzAddress.Signature,
Overlay: s.bzzAddress.Overlay.Bytes(),
ObservedUnderlay: fullRemoteMABytes,
},
Ack: &pb.Ack{
Overlay: bzzAddress.Overlay.Bytes(),
Signature: bzzAddress.Signature,
NetworkID: s.networkID,
Light: s.lightNode,
},
Ack: &pb.Ack{BzzAddress: req.BzzAddress},
}); err != nil {
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
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
}
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{
BzzAddress: bzzAddress,
Light: req.Light,
BzzAddress: remoteBzzAddress,
Light: ack.Light,
}, nil
}
......@@ -180,14 +194,21 @@ func (s *Service) Disconnected(_ network.Network, c network.Conn) {
delete(s.receivedHandshakes, c.RemotePeer())
}
func (s *Service) checkAck(ack *pb.Ack) error {
if !bytes.Equal(ack.BzzAddress.Overlay, s.bzzAddress.Overlay.Bytes()) ||
!bytes.Equal(ack.BzzAddress.Underlay, s.bzzAddress.Underlay.Bytes()) ||
!bytes.Equal(ack.BzzAddress.Signature, s.bzzAddress.Signature) {
return ErrInvalidAck
func buildFullMA(addr ma.Multiaddr, peerID libp2ppeer.ID) (ma.Multiaddr, error) {
return ma.NewMultiaddr(fmt.Sprintf("%s/p2p/%s", addr.String(), peerID.Pretty()))
}
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 {
......
......@@ -9,22 +9,17 @@ package handshake;
option go_package = "pb";
message Syn {
BzzAddress BzzAddress = 1;
uint64 NetworkID = 2;
bool Light = 3;
bytes ObservedUnderlay = 1;
}
message Ack {
BzzAddress BzzAddress = 1;
bytes Overlay = 1;
bytes Signature = 2;
uint64 NetworkID = 3;
bool Light = 4;
}
message SynAck {
Syn Syn = 1;
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 {
PrivateKey *ecdsa.PrivateKey
DisableWS bool
DisableQUIC bool
LightNode bool
Addressbook addressbook.Putter
Logger logging.Logger
Tracer *tracing.Tracer
......@@ -156,13 +157,7 @@ func New(ctx context.Context, signer beecrypto.Signer, networkID uint64, overlay
return nil, fmt.Errorf("autonat: %w", err)
}
// todo: handle different underlays
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)
handshakeService, err := handshake.New(overlay, signer, networkID, o.LightNode, o.Logger)
if err != nil {
return nil, fmt.Errorf("handshake service: %w", err)
}
......@@ -192,7 +187,7 @@ func New(ctx context.Context, signer beecrypto.Signer, networkID uint64, overlay
// handshake
s.host.SetStreamHandlerMatch(id, matcher, func(stream network.Stream) {
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 {
s.logger.Debugf("handshake: handle %s: %v", peerID, err)
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.
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 {
_ = s.disconnect(info.ID)
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