Skip to content

Commit fb25e78

Browse files
committed
Gossip certStore fix
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 <[email protected]> Change-Id: If8a366f06242bdf990dcef7c6dcce7c2ad974f83
1 parent 384e294 commit fb25e78

File tree

3 files changed

+313
-33
lines changed

3 files changed

+313
-33
lines changed

gossip/gossip/certstore.go

+35-8
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ limitations under the License.
1717
package gossip
1818

1919
import (
20+
"bytes"
21+
"fmt"
2022
"sync"
2123

2224
prot "github.com/golang/protobuf/proto"
@@ -27,7 +29,6 @@ import (
2729
"github.com/hyperledger/fabric/gossip/identity"
2830
"github.com/hyperledger/fabric/gossip/proto"
2931
"github.com/hyperledger/fabric/gossip/util"
30-
"fmt"
3132
)
3233

3334
// certStore supports pull dissemination of identity messages
@@ -37,7 +38,7 @@ type certStore struct {
3738
idMapper identity.Mapper
3839
pull pull.Mediator
3940
logger *util.Logger
40-
mcs api.MessageCryptoService
41+
mcs api.MessageCryptoService
4142
}
4243

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

5152
certStore := &certStore{
52-
mcs: mcs,
53+
mcs: mcs,
5354
pull: puller,
5455
idMapper: idMapper,
5556
selfIdentity: selfIdentity,
@@ -82,21 +83,47 @@ func newCertStore(puller pull.Mediator, idMapper identity.Mapper, selfIdentity a
8283
func (cs *certStore) handleMessage(msg comm.ReceivedMessage) {
8384
if update := msg.GetGossipMessage().GetDataUpdate(); update != nil {
8485
for _, m := range update.Data {
85-
if ! m.IsIdentityMsg() {
86+
if !m.IsIdentityMsg() {
8687
cs.logger.Warning("Got a non-identity message:", m, "aborting")
8788
return
8889
}
89-
idMsg := m.GetPeerIdentity()
90-
if err := cs.mcs.ValidateIdentity(api.PeerIdentityType(idMsg.Cert)); err != nil {
91-
cs.logger.Warning("Got invalid certificate:", err)
90+
if err := cs.validateIdentityMsg(m); err != nil {
91+
cs.logger.Warning("Failed validating identity message:", err)
9292
return
9393
}
94-
9594
}
9695
}
9796
cs.pull.HandleMessage(msg)
9897
}
9998

99+
func (cs *certStore) validateIdentityMsg(msg *proto.GossipMessage) error {
100+
idMsg := msg.GetPeerIdentity()
101+
if idMsg == nil {
102+
return fmt.Errorf("Identity empty:", msg)
103+
}
104+
pkiID := idMsg.PkiID
105+
cert := idMsg.Cert
106+
sig := idMsg.Sig
107+
calculatedPKIID := cs.mcs.GetPKIidOfCert(api.PeerIdentityType(cert))
108+
claimedPKIID := common.PKIidType(pkiID)
109+
if !bytes.Equal(calculatedPKIID, claimedPKIID) {
110+
return fmt.Errorf("Calculated pkiID doesn't match identity: calculated: %v, claimedPKI-ID: %v", calculatedPKIID, claimedPKIID)
111+
}
112+
113+
idMsg.Sig = nil
114+
b, err := prot.Marshal(idMsg)
115+
if err != nil {
116+
return fmt.Errorf("Failed marshalling: %v", err)
117+
}
118+
err = cs.mcs.Verify(api.PeerIdentityType(cert), sig, b)
119+
if err != nil {
120+
return fmt.Errorf("Failed verifying message: %v", err)
121+
}
122+
idMsg.Sig = sig
123+
124+
return cs.mcs.ValidateIdentity(api.PeerIdentityType(idMsg.Cert))
125+
}
126+
100127
func (cs *certStore) createIdentityMessage() *proto.GossipMessage {
101128
identity := &proto.PeerIdentity{
102129
Cert: cs.selfIdentity,

gossip/gossip/certstore_test.go

+271
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
/*
2+
Copyright IBM Corp. 2016 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package gossip
18+
19+
import (
20+
"sync"
21+
"sync/atomic"
22+
"testing"
23+
"time"
24+
25+
prot "github.com/golang/protobuf/proto"
26+
"github.com/hyperledger/fabric/gossip/api"
27+
"github.com/hyperledger/fabric/gossip/comm"
28+
"github.com/hyperledger/fabric/gossip/common"
29+
"github.com/hyperledger/fabric/gossip/discovery"
30+
"github.com/hyperledger/fabric/gossip/gossip/algo"
31+
"github.com/hyperledger/fabric/gossip/gossip/pull"
32+
"github.com/hyperledger/fabric/gossip/identity"
33+
"github.com/hyperledger/fabric/gossip/proto"
34+
"github.com/stretchr/testify/assert"
35+
"github.com/stretchr/testify/mock"
36+
)
37+
38+
func init() {
39+
shortenedWaitTime := time.Millisecond * 300
40+
algo.SetDigestWaitTime(shortenedWaitTime / 2)
41+
algo.SetRequestWaitTime(shortenedWaitTime)
42+
algo.SetResponseWaitTime(shortenedWaitTime)
43+
}
44+
45+
type pullerMock struct {
46+
mock.Mock
47+
pull.Mediator
48+
}
49+
50+
type sentMsg struct {
51+
msg *proto.GossipMessage
52+
mock.Mock
53+
}
54+
55+
func (s *sentMsg) Respond(msg *proto.GossipMessage) {
56+
s.Called(msg)
57+
}
58+
59+
func (s *sentMsg) GetGossipMessage() *proto.GossipMessage {
60+
return s.msg
61+
}
62+
63+
func (s *sentMsg) GetPKIID() common.PKIidType {
64+
return nil
65+
}
66+
67+
type senderMock struct {
68+
mock.Mock
69+
}
70+
71+
func (s *senderMock) Send(msg *proto.GossipMessage, peers ...*comm.RemotePeer) {
72+
s.Called(msg, peers)
73+
}
74+
75+
type membershipSvcMock struct {
76+
mock.Mock
77+
}
78+
79+
func (m *membershipSvcMock) GetMembership() []discovery.NetworkMember {
80+
args := m.Called()
81+
return args.Get(0).([]discovery.NetworkMember)
82+
}
83+
84+
func TestCertStoreBadSignature(t *testing.T) {
85+
t.Parallel()
86+
badSignature := func(nonce uint64) comm.ReceivedMessage {
87+
return createUpdateMessage(nonce, createBadlySignedUpdateMessage())
88+
}
89+
90+
testCertificateUpdate(t, badSignature, false)
91+
}
92+
93+
func TestCertStoreMismatchedIdentity(t *testing.T) {
94+
t.Parallel()
95+
mismatchedIdentity := func(nonce uint64) comm.ReceivedMessage {
96+
return createUpdateMessage(nonce, createMismatchedUpdateMessage())
97+
}
98+
99+
testCertificateUpdate(t, mismatchedIdentity, false)
100+
}
101+
102+
func TestCertStoreShouldSucceed(t *testing.T) {
103+
t.Parallel()
104+
totallyFineIdentity := func(nonce uint64) comm.ReceivedMessage {
105+
return createUpdateMessage(nonce, createValidUpdateMessage())
106+
}
107+
108+
testCertificateUpdate(t, totallyFineIdentity, true)
109+
}
110+
111+
func testCertificateUpdate(t *testing.T, updateFactory func(uint64) comm.ReceivedMessage, shouldSucceed bool) {
112+
config := pull.PullConfig{
113+
MsgType: proto.PullMsgType_IdentityMsg,
114+
PeerCountToSelect: 1,
115+
PullInterval: time.Millisecond * 500,
116+
Tag: proto.GossipMessage_EMPTY,
117+
Channel: nil,
118+
Id: "id1",
119+
}
120+
sender := &senderMock{}
121+
memberSvc := &membershipSvcMock{}
122+
memberSvc.On("GetMembership").Return([]discovery.NetworkMember{{PKIid: []byte("bla bla"), Endpoint: "localhost:5611"}})
123+
124+
pullMediator := pull.NewPullMediator(config,
125+
sender,
126+
memberSvc,
127+
func(msg *proto.GossipMessage) string { return string(msg.GetPeerIdentity().PkiID) },
128+
func(msg *proto.GossipMessage) {})
129+
certStore := newCertStore(&pullerMock{
130+
Mediator: pullMediator,
131+
}, identity.NewIdentityMapper(&naiveCryptoService{}), api.PeerIdentityType("SELF"), &naiveCryptoService{})
132+
133+
wg := sync.WaitGroup{}
134+
wg.Add(1)
135+
sentHello := int32(0)
136+
sender.On("Send", mock.Anything, mock.Anything).Run(func(arg mock.Arguments) {
137+
msg := arg.Get(0).(*proto.GossipMessage)
138+
if hello := msg.GetHello(); hello != nil && atomic.LoadInt32(&sentHello) == int32(0) {
139+
atomic.StoreInt32(&sentHello, int32(1))
140+
go certStore.handleMessage(createDigest(hello.Nonce))
141+
}
142+
143+
if dataReq := msg.GetDataReq(); dataReq != nil {
144+
certStore.handleMessage(updateFactory(dataReq.Nonce))
145+
wg.Done()
146+
}
147+
})
148+
wg.Wait()
149+
150+
hello := &sentMsg{
151+
msg: &proto.GossipMessage{
152+
Channel: []byte(""),
153+
Tag: proto.GossipMessage_EMPTY,
154+
Content: &proto.GossipMessage_Hello{
155+
Hello: &proto.GossipHello{
156+
Nonce: 0,
157+
Metadata: nil,
158+
MsgType: proto.PullMsgType_IdentityMsg,
159+
},
160+
},
161+
},
162+
}
163+
responseChan := make(chan *proto.GossipMessage, 1)
164+
hello.On("Respond", mock.Anything).Run(func(arg mock.Arguments) {
165+
msg := arg.Get(0).(*proto.GossipMessage)
166+
assert.NotNil(t, msg.GetDataDig())
167+
responseChan <- msg
168+
})
169+
certStore.handleMessage(hello)
170+
select {
171+
case msg := <-responseChan:
172+
if shouldSucceed {
173+
assert.Len(t, msg.GetDataDig().Digests, 2, "Valid identity hasn't entered the certStore")
174+
} else {
175+
assert.Len(t, msg.GetDataDig().Digests, 1, "Mismatched identity has been injected into certStore")
176+
}
177+
case <-time.After(time.Second):
178+
t.Fatalf("Didn't respond with a digest message in a timely manner")
179+
}
180+
}
181+
182+
func createMismatchedUpdateMessage() *proto.GossipMessage {
183+
identity := &proto.PeerIdentity{
184+
// This PKI-ID is different than the cert, and the mapping between
185+
// certificate to PKI-ID in this test is simply the identity function.
186+
PkiID: []byte("A"),
187+
Cert: []byte("D"),
188+
}
189+
190+
b, _ := prot.Marshal(identity)
191+
identity.Sig, _ = (&naiveCryptoService{}).Sign(b)
192+
return &proto.GossipMessage{
193+
Channel: nil,
194+
Nonce: 0,
195+
Tag: proto.GossipMessage_EMPTY,
196+
Content: &proto.GossipMessage_PeerIdentity{
197+
PeerIdentity: identity,
198+
},
199+
}
200+
}
201+
202+
func createBadlySignedUpdateMessage() *proto.GossipMessage {
203+
identity := &proto.PeerIdentity{
204+
PkiID: []byte("C"),
205+
Cert: []byte("C"),
206+
}
207+
208+
b, _ := prot.Marshal(identity)
209+
identity.Sig, _ = (&naiveCryptoService{}).Sign(b)
210+
// This would simulate a bad sig
211+
if identity.Sig[0] == 0 {
212+
identity.Sig[0] = 1
213+
} else {
214+
identity.Sig[0] = 0
215+
}
216+
217+
return &proto.GossipMessage{
218+
Channel: nil,
219+
Nonce: 0,
220+
Tag: proto.GossipMessage_EMPTY,
221+
Content: &proto.GossipMessage_PeerIdentity{
222+
PeerIdentity: identity,
223+
},
224+
}
225+
}
226+
227+
func createValidUpdateMessage() *proto.GossipMessage {
228+
identity := &proto.PeerIdentity{
229+
PkiID: []byte("B"),
230+
Cert: []byte("B"),
231+
}
232+
233+
b, _ := prot.Marshal(identity)
234+
identity.Sig, _ = (&naiveCryptoService{}).Sign(b)
235+
return &proto.GossipMessage{
236+
Channel: nil,
237+
Nonce: 0,
238+
Tag: proto.GossipMessage_EMPTY,
239+
Content: &proto.GossipMessage_PeerIdentity{
240+
PeerIdentity: identity,
241+
},
242+
}
243+
}
244+
245+
func createUpdateMessage(nonce uint64, idMsg *proto.GossipMessage) comm.ReceivedMessage {
246+
update := &proto.GossipMessage{
247+
Tag: proto.GossipMessage_EMPTY,
248+
Content: &proto.GossipMessage_DataUpdate{
249+
DataUpdate: &proto.DataUpdate{
250+
MsgType: proto.PullMsgType_IdentityMsg,
251+
Nonce: nonce,
252+
Data: []*proto.GossipMessage{idMsg},
253+
},
254+
},
255+
}
256+
return &sentMsg{msg: update}
257+
}
258+
259+
func createDigest(nonce uint64) comm.ReceivedMessage {
260+
digest := &proto.GossipMessage{
261+
Tag: proto.GossipMessage_EMPTY,
262+
Content: &proto.GossipMessage_DataDig{
263+
DataDig: &proto.DataDigest{
264+
Nonce: nonce,
265+
MsgType: proto.PullMsgType_IdentityMsg,
266+
Digests: []string{"A", "C"},
267+
},
268+
},
269+
}
270+
return &sentMsg{msg: digest}
271+
}

0 commit comments

Comments
 (0)