Skip to content

Commit

Permalink
Gossip certStore fix
Browse files Browse the repository at this point in the history
Forgot to call validateIdentityMsg to the cert in the certStore
And also to check corralation between calculated PKIID and actual PKIIID
of alive messages.
Added a test that tests both conditions
as well as a "positive" flow.

Signed-off-by: Yacov Manevich <yacovm@il.ibm.com>
Change-Id: If8a366f06242bdf990dcef7c6dcce7c2ad974f83
  • Loading branch information
yacovm committed Dec 23, 2016
1 parent 384e294 commit fb25e78
Show file tree
Hide file tree
Showing 3 changed files with 313 additions and 33 deletions.
43 changes: 35 additions & 8 deletions gossip/gossip/certstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ limitations under the License.
package gossip

import (
"bytes"
"fmt"
"sync"

prot "github.com/golang/protobuf/proto"
Expand All @@ -27,7 +29,6 @@ import (
"github.com/hyperledger/fabric/gossip/identity"
"github.com/hyperledger/fabric/gossip/proto"
"github.com/hyperledger/fabric/gossip/util"
"fmt"
)

// certStore supports pull dissemination of identity messages
Expand All @@ -37,7 +38,7 @@ type certStore struct {
idMapper identity.Mapper
pull pull.Mediator
logger *util.Logger
mcs api.MessageCryptoService
mcs api.MessageCryptoService
}

func newCertStore(puller pull.Mediator, idMapper identity.Mapper, selfIdentity api.PeerIdentityType, mcs api.MessageCryptoService) *certStore {
Expand All @@ -49,7 +50,7 @@ func newCertStore(puller pull.Mediator, idMapper identity.Mapper, selfIdentity a
}

certStore := &certStore{
mcs: mcs,
mcs: mcs,
pull: puller,
idMapper: idMapper,
selfIdentity: selfIdentity,
Expand Down Expand Up @@ -82,21 +83,47 @@ func newCertStore(puller pull.Mediator, idMapper identity.Mapper, selfIdentity a
func (cs *certStore) handleMessage(msg comm.ReceivedMessage) {
if update := msg.GetGossipMessage().GetDataUpdate(); update != nil {
for _, m := range update.Data {
if ! m.IsIdentityMsg() {
if !m.IsIdentityMsg() {
cs.logger.Warning("Got a non-identity message:", m, "aborting")
return
}
idMsg := m.GetPeerIdentity()
if err := cs.mcs.ValidateIdentity(api.PeerIdentityType(idMsg.Cert)); err != nil {
cs.logger.Warning("Got invalid certificate:", err)
if err := cs.validateIdentityMsg(m); err != nil {
cs.logger.Warning("Failed validating identity message:", err)
return
}

}
}
cs.pull.HandleMessage(msg)
}

func (cs *certStore) validateIdentityMsg(msg *proto.GossipMessage) error {
idMsg := msg.GetPeerIdentity()
if idMsg == nil {
return fmt.Errorf("Identity empty:", msg)
}
pkiID := idMsg.PkiID
cert := idMsg.Cert
sig := idMsg.Sig
calculatedPKIID := cs.mcs.GetPKIidOfCert(api.PeerIdentityType(cert))
claimedPKIID := common.PKIidType(pkiID)
if !bytes.Equal(calculatedPKIID, claimedPKIID) {
return fmt.Errorf("Calculated pkiID doesn't match identity: calculated: %v, claimedPKI-ID: %v", calculatedPKIID, claimedPKIID)
}

idMsg.Sig = nil
b, err := prot.Marshal(idMsg)
if err != nil {
return fmt.Errorf("Failed marshalling: %v", err)
}
err = cs.mcs.Verify(api.PeerIdentityType(cert), sig, b)
if err != nil {
return fmt.Errorf("Failed verifying message: %v", err)
}
idMsg.Sig = sig

return cs.mcs.ValidateIdentity(api.PeerIdentityType(idMsg.Cert))
}

func (cs *certStore) createIdentityMessage() *proto.GossipMessage {
identity := &proto.PeerIdentity{
Cert: cs.selfIdentity,
Expand Down
271 changes: 271 additions & 0 deletions gossip/gossip/certstore_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
/*
Copyright IBM Corp. 2016 All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package gossip

import (
"sync"
"sync/atomic"
"testing"
"time"

prot "github.com/golang/protobuf/proto"
"github.com/hyperledger/fabric/gossip/api"
"github.com/hyperledger/fabric/gossip/comm"
"github.com/hyperledger/fabric/gossip/common"
"github.com/hyperledger/fabric/gossip/discovery"
"github.com/hyperledger/fabric/gossip/gossip/algo"
"github.com/hyperledger/fabric/gossip/gossip/pull"
"github.com/hyperledger/fabric/gossip/identity"
"github.com/hyperledger/fabric/gossip/proto"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

func init() {
shortenedWaitTime := time.Millisecond * 300
algo.SetDigestWaitTime(shortenedWaitTime / 2)
algo.SetRequestWaitTime(shortenedWaitTime)
algo.SetResponseWaitTime(shortenedWaitTime)
}

type pullerMock struct {
mock.Mock
pull.Mediator
}

type sentMsg struct {
msg *proto.GossipMessage
mock.Mock
}

func (s *sentMsg) Respond(msg *proto.GossipMessage) {
s.Called(msg)
}

func (s *sentMsg) GetGossipMessage() *proto.GossipMessage {
return s.msg
}

func (s *sentMsg) GetPKIID() common.PKIidType {
return nil
}

type senderMock struct {
mock.Mock
}

func (s *senderMock) Send(msg *proto.GossipMessage, peers ...*comm.RemotePeer) {
s.Called(msg, peers)
}

type membershipSvcMock struct {
mock.Mock
}

func (m *membershipSvcMock) GetMembership() []discovery.NetworkMember {
args := m.Called()
return args.Get(0).([]discovery.NetworkMember)
}

func TestCertStoreBadSignature(t *testing.T) {
t.Parallel()
badSignature := func(nonce uint64) comm.ReceivedMessage {
return createUpdateMessage(nonce, createBadlySignedUpdateMessage())
}

testCertificateUpdate(t, badSignature, false)
}

func TestCertStoreMismatchedIdentity(t *testing.T) {
t.Parallel()
mismatchedIdentity := func(nonce uint64) comm.ReceivedMessage {
return createUpdateMessage(nonce, createMismatchedUpdateMessage())
}

testCertificateUpdate(t, mismatchedIdentity, false)
}

func TestCertStoreShouldSucceed(t *testing.T) {
t.Parallel()
totallyFineIdentity := func(nonce uint64) comm.ReceivedMessage {
return createUpdateMessage(nonce, createValidUpdateMessage())
}

testCertificateUpdate(t, totallyFineIdentity, true)
}

func testCertificateUpdate(t *testing.T, updateFactory func(uint64) comm.ReceivedMessage, shouldSucceed bool) {
config := pull.PullConfig{
MsgType: proto.PullMsgType_IdentityMsg,
PeerCountToSelect: 1,
PullInterval: time.Millisecond * 500,
Tag: proto.GossipMessage_EMPTY,
Channel: nil,
Id: "id1",
}
sender := &senderMock{}
memberSvc := &membershipSvcMock{}
memberSvc.On("GetMembership").Return([]discovery.NetworkMember{{PKIid: []byte("bla bla"), Endpoint: "localhost:5611"}})

pullMediator := pull.NewPullMediator(config,
sender,
memberSvc,
func(msg *proto.GossipMessage) string { return string(msg.GetPeerIdentity().PkiID) },
func(msg *proto.GossipMessage) {})
certStore := newCertStore(&pullerMock{
Mediator: pullMediator,
}, identity.NewIdentityMapper(&naiveCryptoService{}), api.PeerIdentityType("SELF"), &naiveCryptoService{})

wg := sync.WaitGroup{}
wg.Add(1)
sentHello := int32(0)
sender.On("Send", mock.Anything, mock.Anything).Run(func(arg mock.Arguments) {
msg := arg.Get(0).(*proto.GossipMessage)
if hello := msg.GetHello(); hello != nil && atomic.LoadInt32(&sentHello) == int32(0) {
atomic.StoreInt32(&sentHello, int32(1))
go certStore.handleMessage(createDigest(hello.Nonce))
}

if dataReq := msg.GetDataReq(); dataReq != nil {
certStore.handleMessage(updateFactory(dataReq.Nonce))
wg.Done()
}
})
wg.Wait()

hello := &sentMsg{
msg: &proto.GossipMessage{
Channel: []byte(""),
Tag: proto.GossipMessage_EMPTY,
Content: &proto.GossipMessage_Hello{
Hello: &proto.GossipHello{
Nonce: 0,
Metadata: nil,
MsgType: proto.PullMsgType_IdentityMsg,
},
},
},
}
responseChan := make(chan *proto.GossipMessage, 1)
hello.On("Respond", mock.Anything).Run(func(arg mock.Arguments) {
msg := arg.Get(0).(*proto.GossipMessage)
assert.NotNil(t, msg.GetDataDig())
responseChan <- msg
})
certStore.handleMessage(hello)
select {
case msg := <-responseChan:
if shouldSucceed {
assert.Len(t, msg.GetDataDig().Digests, 2, "Valid identity hasn't entered the certStore")
} else {
assert.Len(t, msg.GetDataDig().Digests, 1, "Mismatched identity has been injected into certStore")
}
case <-time.After(time.Second):
t.Fatalf("Didn't respond with a digest message in a timely manner")
}
}

func createMismatchedUpdateMessage() *proto.GossipMessage {
identity := &proto.PeerIdentity{
// This PKI-ID is different than the cert, and the mapping between
// certificate to PKI-ID in this test is simply the identity function.
PkiID: []byte("A"),
Cert: []byte("D"),
}

b, _ := prot.Marshal(identity)
identity.Sig, _ = (&naiveCryptoService{}).Sign(b)
return &proto.GossipMessage{
Channel: nil,
Nonce: 0,
Tag: proto.GossipMessage_EMPTY,
Content: &proto.GossipMessage_PeerIdentity{
PeerIdentity: identity,
},
}
}

func createBadlySignedUpdateMessage() *proto.GossipMessage {
identity := &proto.PeerIdentity{
PkiID: []byte("C"),
Cert: []byte("C"),
}

b, _ := prot.Marshal(identity)
identity.Sig, _ = (&naiveCryptoService{}).Sign(b)
// This would simulate a bad sig
if identity.Sig[0] == 0 {
identity.Sig[0] = 1
} else {
identity.Sig[0] = 0
}

return &proto.GossipMessage{
Channel: nil,
Nonce: 0,
Tag: proto.GossipMessage_EMPTY,
Content: &proto.GossipMessage_PeerIdentity{
PeerIdentity: identity,
},
}
}

func createValidUpdateMessage() *proto.GossipMessage {
identity := &proto.PeerIdentity{
PkiID: []byte("B"),
Cert: []byte("B"),
}

b, _ := prot.Marshal(identity)
identity.Sig, _ = (&naiveCryptoService{}).Sign(b)
return &proto.GossipMessage{
Channel: nil,
Nonce: 0,
Tag: proto.GossipMessage_EMPTY,
Content: &proto.GossipMessage_PeerIdentity{
PeerIdentity: identity,
},
}
}

func createUpdateMessage(nonce uint64, idMsg *proto.GossipMessage) comm.ReceivedMessage {
update := &proto.GossipMessage{
Tag: proto.GossipMessage_EMPTY,
Content: &proto.GossipMessage_DataUpdate{
DataUpdate: &proto.DataUpdate{
MsgType: proto.PullMsgType_IdentityMsg,
Nonce: nonce,
Data: []*proto.GossipMessage{idMsg},
},
},
}
return &sentMsg{msg: update}
}

func createDigest(nonce uint64) comm.ReceivedMessage {
digest := &proto.GossipMessage{
Tag: proto.GossipMessage_EMPTY,
Content: &proto.GossipMessage_DataDig{
DataDig: &proto.DataDigest{
Nonce: nonce,
MsgType: proto.PullMsgType_IdentityMsg,
Digests: []string{"A", "C"},
},
},
}
return &sentMsg{msg: digest}
}
Loading

0 comments on commit fb25e78

Please sign in to comment.