Skip to content

Commit

Permalink
[FAB-2007] Gossip/Comm deep probing
Browse files Browse the repository at this point in the history
When a peer receives a join-channel event, it doesn't know whether
the anchor peers in the genesis block indeed match their said organizations.
A malicious attacker may create a channel and add an anchor peer of
its own in each organization, and this way- make peers expose
their internal endpoints which is not wanted.

This commit introduces another method - Handkshake() that
performs a handshake to the remote peer, and returns its identity,
or an error if either a communication or authentication error occures.
The certificate then can be used to extract the remote peers' organization.

Change-Id: I0a0e71a10cb5831f07c72be28308cadb486c1d3a
Signed-off-by: Yacov Manevich <[email protected]>
  • Loading branch information
yacovm committed Mar 8, 2017
1 parent 821c9d8 commit 5eb459a
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 3 deletions.
8 changes: 7 additions & 1 deletion gossip/comm/comm.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package comm
import (
"fmt"

"github.com/hyperledger/fabric/gossip/api"
"github.com/hyperledger/fabric/gossip/common"
proto "github.com/hyperledger/fabric/protos/gossip"
)
Expand All @@ -33,9 +34,14 @@ type Comm interface {
// Send sends a message to remote peers
Send(msg *proto.SignedGossipMessage, peers ...*RemotePeer)

// Probe probes a remote node and returns nil if its responsive
// Probe probes a remote node and returns nil if its responsive,
// and an error if it's not.
Probe(peer *RemotePeer) error

// Handshake authenticates a remote peer and returns
// (its identity, nil) on success and (nil, error)
Handshake(peer *RemotePeer) (api.PeerIdentityType, error)

// Accept returns a dedicated read-only channel for messages sent by other nodes that match a certain predicate.
// Each message from the channel can be used to send a reply back to the sender
Accept(common.MessageAcceptor) <-chan proto.ReceivedMessage
Expand Down
26 changes: 26 additions & 0 deletions gossip/comm/comm_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,32 @@ func (c *commImpl) Probe(remotePeer *RemotePeer) error {
return err
}

func (c *commImpl) Handshake(remotePeer *RemotePeer) (api.PeerIdentityType, error) {
cc, err := grpc.Dial(remotePeer.Endpoint, append(c.opts, grpc.WithBlock())...)
if err != nil {
return nil, err
}
defer cc.Close()

cl := proto.NewGossipClient(cc)
if _, err = cl.Ping(context.Background(), &proto.Empty{}); err != nil {
return nil, err
}

stream, err := cl.GossipStream(context.Background())
if err != nil {
return nil, err
}
connInfo, err := c.authenticateRemotePeer(stream)
if err != nil {
return nil, err
}
if len(remotePeer.PKIID) > 0 && !bytes.Equal(connInfo.ID, remotePeer.PKIID) {
return nil, errors.New("PKI-ID of remote peer doesn't match expected PKI-ID")
}
return connInfo.Identity, nil
}

func (c *commImpl) Accept(acceptor common.MessageAcceptor) <-chan proto.ReceivedMessage {
genericChan := c.msgPublisher.AddChannel(acceptor)
specificChan := make(chan proto.ReceivedMessage, 10)
Expand Down
23 changes: 23 additions & 0 deletions gossip/comm/comm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -487,15 +487,38 @@ func TestProbe(t *testing.T) {
comm2, _ := newCommInstance(6612, naiveSec)
time.Sleep(time.Duration(1) * time.Second)
assert.NoError(t, comm1.Probe(remotePeer(6612)))
_, err := comm1.Handshake(remotePeer(6612))
assert.NoError(t, err)
assert.Error(t, comm1.Probe(remotePeer(9012)))
_, err = comm1.Handshake(remotePeer(9012))
assert.Error(t, err)
comm2.Stop()
time.Sleep(time.Second)
assert.Error(t, comm1.Probe(remotePeer(6612)))
_, err = comm1.Handshake(remotePeer(6612))
assert.Error(t, err)
comm2, _ = newCommInstance(6612, naiveSec)
defer comm2.Stop()
time.Sleep(time.Duration(1) * time.Second)
assert.NoError(t, comm2.Probe(remotePeer(6611)))
_, err = comm2.Handshake(remotePeer(6611))
assert.NoError(t, err)
assert.NoError(t, comm1.Probe(remotePeer(6612)))
_, err = comm1.Handshake(remotePeer(6612))
assert.NoError(t, err)
// Now try a deep probe with an expected PKI-ID that doesn't match
wrongRemotePeer := remotePeer(6612)
if wrongRemotePeer.PKIID[0] == 0 {
wrongRemotePeer.PKIID[0] = 1
} else {
wrongRemotePeer.PKIID[0] = 0
}
_, err = comm1.Handshake(wrongRemotePeer)
assert.Error(t, err)
// Try a deep probe with a nil PKI-ID
id, err := comm1.Handshake(&RemotePeer{Endpoint: "localhost:6612"})
assert.NoError(t, err)
assert.Equal(t, api.PeerIdentityType("localhost:6612"), id)
}

func TestPresumedDead(t *testing.T) {
Expand Down
10 changes: 9 additions & 1 deletion gossip/comm/mock/mock_comm.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package mock

import (
"github.com/hyperledger/fabric/gossip/api"
"github.com/hyperledger/fabric/gossip/comm"
"github.com/hyperledger/fabric/gossip/common"
"github.com/hyperledger/fabric/gossip/util"
Expand Down Expand Up @@ -152,11 +153,18 @@ func (mock *commMock) Send(msg *proto.SignedGossipMessage, peers ...*comm.Remote
}
}

// Probe probes a remote node and returns nil if its responsive
// Probe probes a remote node and returns nil if its responsive,
// and an error if it's not.
func (mock *commMock) Probe(peer *comm.RemotePeer) error {
return nil
}

// Handshake authenticates a remote peer and returns
// (its identity, nil) on success and (nil, error)
func (mock *commMock) Handshake(peer *comm.RemotePeer) (api.PeerIdentityType, error) {
return nil, nil
}

// Accept returns a dedicated read-only channel for messages sent by other nodes that match a certain predicate.
// Each message from the channel can be used to send a reply back to the sender
func (mock *commMock) Accept(accept common.MessageAcceptor) <-chan proto.ReceivedMessage {
Expand Down
3 changes: 2 additions & 1 deletion gossip/gossip/gossip_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,8 @@ func (da *discoveryAdapter) SendToPeer(peer *discovery.NetworkMember, msg *proto
}

func (da *discoveryAdapter) Ping(peer *discovery.NetworkMember) bool {
return da.c.Probe(&comm.RemotePeer{Endpoint: peer.PreferredEndpoint(), PKIID: peer.PKIid}) == nil
err := da.c.Probe(&comm.RemotePeer{Endpoint: peer.PreferredEndpoint(), PKIID: peer.PKIid})
return err == nil
}

func (da *discoveryAdapter) Accept() <-chan *proto.SignedGossipMessage {
Expand Down

0 comments on commit 5eb459a

Please sign in to comment.