github.com/yimialmonte/fabric@v2.1.1+incompatible/gossip/gossip/certstore_test.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package gossip 8 9 import ( 10 "bytes" 11 "fmt" 12 "sync" 13 "testing" 14 "time" 15 16 proto "github.com/hyperledger/fabric-protos-go/gossip" 17 "github.com/hyperledger/fabric/gossip/api" 18 "github.com/hyperledger/fabric/gossip/comm" 19 "github.com/hyperledger/fabric/gossip/common" 20 "github.com/hyperledger/fabric/gossip/discovery" 21 "github.com/hyperledger/fabric/gossip/gossip/algo" 22 "github.com/hyperledger/fabric/gossip/gossip/pull" 23 "github.com/hyperledger/fabric/gossip/identity" 24 "github.com/hyperledger/fabric/gossip/protoext" 25 "github.com/hyperledger/fabric/gossip/util" 26 "github.com/stretchr/testify/assert" 27 "github.com/stretchr/testify/mock" 28 ) 29 30 func init() { 31 util.SetupTestLogging() 32 } 33 34 var ( 35 cs = &naiveCryptoService{ 36 revokedPkiIDS: make(map[string]struct{}), 37 } 38 ) 39 40 type pullerMock struct { 41 mock.Mock 42 pull.Mediator 43 } 44 45 type sentMsg struct { 46 msg *protoext.SignedGossipMessage 47 mock.Mock 48 } 49 50 // GetSourceEnvelope Returns the SignedGossipMessage the ReceivedMessage was 51 // constructed with 52 func (s *sentMsg) GetSourceEnvelope() *proto.Envelope { 53 return nil 54 } 55 56 // Ack returns to the sender an acknowledgement for the message 57 func (s *sentMsg) Ack(err error) { 58 59 } 60 61 func (s *sentMsg) Respond(msg *proto.GossipMessage) { 62 s.Called(msg) 63 } 64 65 func (s *sentMsg) GetGossipMessage() *protoext.SignedGossipMessage { 66 return s.msg 67 } 68 69 func (s *sentMsg) GetConnectionInfo() *protoext.ConnectionInfo { 70 return nil 71 } 72 73 type senderMock struct { 74 mock.Mock 75 sync.Mutex 76 } 77 78 func (s *senderMock) Send(msg *protoext.SignedGossipMessage, peers ...*comm.RemotePeer) { 79 s.Lock() 80 defer s.Unlock() 81 s.Called(msg, peers) 82 } 83 84 type membershipSvcMock struct { 85 mock.Mock 86 } 87 88 func (m *membershipSvcMock) GetMembership() []discovery.NetworkMember { 89 args := m.Called() 90 return args.Get(0).([]discovery.NetworkMember) 91 } 92 93 func TestCertStoreBadSignature(t *testing.T) { 94 badSignature := func(nonce uint64) protoext.ReceivedMessage { 95 return createUpdateMessage(nonce, createBadlySignedUpdateMessage()) 96 } 97 pm, cs, _ := createObjects(badSignature, nil) 98 defer pm.Stop() 99 defer cs.stop() 100 testCertificateUpdate(t, false, cs) 101 } 102 103 func TestCertStoreMismatchedIdentity(t *testing.T) { 104 mismatchedIdentity := func(nonce uint64) protoext.ReceivedMessage { 105 return createUpdateMessage(nonce, createMismatchedUpdateMessage()) 106 } 107 108 pm, cs, _ := createObjects(mismatchedIdentity, nil) 109 defer pm.Stop() 110 defer cs.stop() 111 testCertificateUpdate(t, false, cs) 112 } 113 114 func TestCertStoreShouldSucceed(t *testing.T) { 115 totallyFineIdentity := func(nonce uint64) protoext.ReceivedMessage { 116 return createUpdateMessage(nonce, createValidUpdateMessage()) 117 } 118 119 pm, cs, _ := createObjects(totallyFineIdentity, nil) 120 defer pm.Stop() 121 defer cs.stop() 122 testCertificateUpdate(t, true, cs) 123 } 124 125 func TestCertRevocation(t *testing.T) { 126 defer func() { 127 cs.revokedPkiIDS = map[string]struct{}{} 128 }() 129 130 totallyFineIdentity := func(nonce uint64) protoext.ReceivedMessage { 131 return createUpdateMessage(nonce, createValidUpdateMessage()) 132 } 133 134 askedForIdentity := make(chan struct{}, 1) 135 136 pm, cStore, sender := createObjects(totallyFineIdentity, func(message *protoext.SignedGossipMessage) { 137 askedForIdentity <- struct{}{} 138 }) 139 defer cStore.stop() 140 defer pm.Stop() 141 testCertificateUpdate(t, true, cStore) 142 // Should have asked for an identity for the first time 143 assert.Len(t, askedForIdentity, 1) 144 // Drain channel 145 <-askedForIdentity 146 // Now it's 0 147 assert.Len(t, askedForIdentity, 0) 148 149 sentHello := false 150 l := sync.Mutex{} 151 sender.Lock() 152 sender.Mock = mock.Mock{} 153 sender.On("Send", mock.Anything, mock.Anything).Run(func(arg mock.Arguments) { 154 msg := arg.Get(0).(*protoext.SignedGossipMessage) 155 l.Lock() 156 defer l.Unlock() 157 158 if hello := msg.GetHello(); hello != nil && !sentHello { 159 sentHello = true 160 dig := &proto.GossipMessage{ 161 Tag: proto.GossipMessage_EMPTY, 162 Content: &proto.GossipMessage_DataDig{ 163 DataDig: &proto.DataDigest{ 164 Nonce: hello.Nonce, 165 MsgType: proto.PullMsgType_IDENTITY_MSG, 166 Digests: [][]byte{[]byte("B")}, 167 }, 168 }, 169 } 170 sMsg, _ := protoext.NoopSign(dig) 171 go cStore.handleMessage(&sentMsg{msg: sMsg}) 172 } 173 174 if dataReq := msg.GetDataReq(); dataReq != nil { 175 askedForIdentity <- struct{}{} 176 } 177 }) 178 sender.Unlock() 179 testCertificateUpdate(t, true, cStore) 180 // Shouldn't have asked, because already got identity 181 select { 182 case <-time.After(time.Second * 5): 183 case <-askedForIdentity: 184 assert.Fail(t, "Shouldn't have asked for an identity, because we already have it") 185 } 186 assert.Len(t, askedForIdentity, 0) 187 // Revoke the identity 188 cs.revoke(common.PKIidType("B")) 189 cStore.suspectPeers(func(id api.PeerIdentityType) bool { 190 return string(id) == "B" 191 }) 192 193 l.Lock() 194 sentHello = false 195 l.Unlock() 196 197 select { 198 case <-time.After(time.Second * 5): 199 assert.Fail(t, "Didn't ask for identity, but should have. Looks like identity hasn't expired") 200 case <-askedForIdentity: 201 } 202 } 203 204 func TestCertExpiration(t *testing.T) { 205 // Scenario: In this test we make sure that a peer may not expire 206 // its own identity. 207 // This is important because the only way identities are gossiped 208 // transitively is via the pull mechanism. 209 // If a peer's own identity disappears from the pull mediator, 210 // it will never be sent to peers transitively. 211 // The test ensures that self identities don't expire 212 // in the following manner: 213 // It starts a peer and then sleeps twice the identity usage threshold, 214 // in order to make sure that its own identity should be expired. 215 // Then, it starts another peer, and listens to the messages sent 216 // between both peers, and looks for a few identity digests of the first peer. 217 // If such identity digest are detected, it means that the peer 218 // didn't expire its own identity. 219 220 // Backup original usageThreshold value 221 idUsageThreshold := identity.GetIdentityUsageThreshold() 222 identity.SetIdentityUsageThreshold(time.Second) 223 // Restore original usageThreshold value 224 defer identity.SetIdentityUsageThreshold(idUsageThreshold) 225 226 port0, grpc0, certs0, secDialOpts0, _ := util.CreateGRPCLayer() 227 port1, grpc1, certs1, secDialOpts1, _ := util.CreateGRPCLayer() 228 g1 := newGossipInstanceWithGRPC(0, port0, grpc0, certs0, secDialOpts0, 0, port1) 229 defer g1.Stop() 230 time.Sleep(identity.GetIdentityUsageThreshold() * 2) 231 g2 := newGossipInstanceWithGRPC(0, port1, grpc1, certs1, secDialOpts1, 0) 232 defer g2.Stop() 233 234 identities2Detect := 3 235 // Make the channel bigger than needed so goroutines won't get stuck 236 identitiesGotViaPull := make(chan struct{}, identities2Detect+100) 237 acceptIdentityPullMsgs := func(o interface{}) bool { 238 m := o.(protoext.ReceivedMessage).GetGossipMessage() 239 if protoext.IsPullMsg(m.GossipMessage) && protoext.IsDigestMsg(m.GossipMessage) { 240 for _, dig := range m.GetDataDig().Digests { 241 if bytes.Equal(dig, []byte(fmt.Sprintf("127.0.0.1:%d", port0))) { 242 identitiesGotViaPull <- struct{}{} 243 } 244 } 245 } 246 return false 247 } 248 g1.Accept(acceptIdentityPullMsgs, true) 249 for i := 0; i < identities2Detect; i++ { 250 select { 251 case <-identitiesGotViaPull: 252 case <-time.After(time.Second * 15): 253 assert.Fail(t, "Didn't detect an identity gossiped via pull in a timely manner") 254 return 255 } 256 } 257 } 258 259 func testCertificateUpdate(t *testing.T, shouldSucceed bool, certStore *certStore) { 260 msg, _ := protoext.NoopSign(&proto.GossipMessage{ 261 Channel: []byte(""), 262 Tag: proto.GossipMessage_EMPTY, 263 Content: &proto.GossipMessage_Hello{ 264 Hello: &proto.GossipHello{ 265 Nonce: 0, 266 Metadata: nil, 267 MsgType: proto.PullMsgType_IDENTITY_MSG, 268 }, 269 }, 270 }) 271 hello := &sentMsg{ 272 msg: msg, 273 } 274 responseChan := make(chan *proto.GossipMessage, 1) 275 hello.On("Respond", mock.Anything).Run(func(arg mock.Arguments) { 276 msg := arg.Get(0).(*proto.GossipMessage) 277 assert.NotNil(t, msg.GetDataDig()) 278 responseChan <- msg 279 }) 280 certStore.handleMessage(hello) 281 select { 282 case msg := <-responseChan: 283 if shouldSucceed { 284 assert.Len(t, msg.GetDataDig().Digests, 2, "Valid identity hasn't entered the certStore") 285 } else { 286 assert.Len(t, msg.GetDataDig().Digests, 1, "Mismatched identity has been injected into certStore") 287 } 288 case <-time.After(time.Second): 289 t.Fatal("Didn't respond with a digest message in a timely manner") 290 } 291 } 292 293 func createMismatchedUpdateMessage() *protoext.SignedGossipMessage { 294 peeridentity := &proto.PeerIdentity{ 295 // This PKI-ID is different than the cert, and the mapping between 296 // certificate to PKI-ID in this test is simply the identity function. 297 PkiId: []byte("A"), 298 Cert: []byte("D"), 299 } 300 301 signer := func(msg []byte) ([]byte, error) { 302 return (&naiveCryptoService{}).Sign(msg) 303 } 304 m := &proto.GossipMessage{ 305 Channel: nil, 306 Nonce: 0, 307 Tag: proto.GossipMessage_EMPTY, 308 Content: &proto.GossipMessage_PeerIdentity{ 309 PeerIdentity: peeridentity, 310 }, 311 } 312 sMsg := &protoext.SignedGossipMessage{ 313 GossipMessage: m, 314 } 315 sMsg.Sign(signer) 316 return sMsg 317 } 318 319 func createBadlySignedUpdateMessage() *protoext.SignedGossipMessage { 320 peeridentity := &proto.PeerIdentity{ 321 PkiId: []byte("C"), 322 Cert: []byte("C"), 323 } 324 325 signer := func(msg []byte) ([]byte, error) { 326 return (&naiveCryptoService{}).Sign(msg) 327 } 328 329 m := &proto.GossipMessage{ 330 Channel: nil, 331 Nonce: 0, 332 Tag: proto.GossipMessage_EMPTY, 333 Content: &proto.GossipMessage_PeerIdentity{ 334 PeerIdentity: peeridentity, 335 }, 336 } 337 sMsg := &protoext.SignedGossipMessage{ 338 GossipMessage: m, 339 } 340 sMsg.Sign(signer) 341 // This would simulate a bad sig 342 if sMsg.Envelope.Signature[0] == 0 { 343 sMsg.Envelope.Signature[0] = 1 344 } else { 345 sMsg.Envelope.Signature[0] = 0 346 } 347 return sMsg 348 } 349 350 func createValidUpdateMessage() *protoext.SignedGossipMessage { 351 peeridentity := &proto.PeerIdentity{ 352 PkiId: []byte("B"), 353 Cert: []byte("B"), 354 } 355 356 signer := func(msg []byte) ([]byte, error) { 357 return (&naiveCryptoService{}).Sign(msg) 358 } 359 m := &proto.GossipMessage{ 360 Channel: nil, 361 Nonce: 0, 362 Tag: proto.GossipMessage_EMPTY, 363 Content: &proto.GossipMessage_PeerIdentity{ 364 PeerIdentity: peeridentity, 365 }, 366 } 367 sMsg := &protoext.SignedGossipMessage{ 368 GossipMessage: m, 369 } 370 sMsg.Sign(signer) 371 return sMsg 372 } 373 374 func createUpdateMessage(nonce uint64, idMsg *protoext.SignedGossipMessage) protoext.ReceivedMessage { 375 update := &proto.GossipMessage{ 376 Tag: proto.GossipMessage_EMPTY, 377 Content: &proto.GossipMessage_DataUpdate{ 378 DataUpdate: &proto.DataUpdate{ 379 MsgType: proto.PullMsgType_IDENTITY_MSG, 380 Nonce: nonce, 381 Data: []*proto.Envelope{idMsg.Envelope}, 382 }, 383 }, 384 } 385 sMsg, _ := protoext.NoopSign(update) 386 return &sentMsg{msg: sMsg} 387 } 388 389 func createDigest(nonce uint64) protoext.ReceivedMessage { 390 digest := &proto.GossipMessage{ 391 Tag: proto.GossipMessage_EMPTY, 392 Content: &proto.GossipMessage_DataDig{ 393 DataDig: &proto.DataDigest{ 394 Nonce: nonce, 395 MsgType: proto.PullMsgType_IDENTITY_MSG, 396 Digests: [][]byte{[]byte("A"), []byte("C")}, 397 }, 398 }, 399 } 400 sMsg, _ := protoext.NoopSign(digest) 401 return &sentMsg{msg: sMsg} 402 } 403 404 func createObjects(updateFactory func(uint64) protoext.ReceivedMessage, msgCons pull.MsgConsumer) (pull.Mediator, *certStore, *senderMock) { 405 if msgCons == nil { 406 msgCons = func(_ *protoext.SignedGossipMessage) {} 407 } 408 shortenedWaitTime := time.Millisecond * 300 409 config := pull.Config{ 410 MsgType: proto.PullMsgType_IDENTITY_MSG, 411 PeerCountToSelect: 1, 412 PullInterval: time.Second, 413 Tag: proto.GossipMessage_EMPTY, 414 Channel: nil, 415 ID: "id1", 416 PullEngineConfig: algo.PullEngineConfig{ 417 DigestWaitTime: shortenedWaitTime / 2, 418 RequestWaitTime: shortenedWaitTime, 419 ResponseWaitTime: shortenedWaitTime, 420 }, 421 } 422 sender := &senderMock{} 423 memberSvc := &membershipSvcMock{} 424 memberSvc.On("GetMembership").Return([]discovery.NetworkMember{{PKIid: []byte("bla bla"), Endpoint: "127.0.0.1:5611"}}) 425 426 var certStore *certStore 427 adapter := &pull.PullAdapter{ 428 Sndr: sender, 429 MsgCons: func(msg *protoext.SignedGossipMessage) { 430 certStore.idMapper.Put(msg.GetPeerIdentity().PkiId, msg.GetPeerIdentity().Cert) 431 msgCons(msg) 432 }, 433 IdExtractor: func(msg *protoext.SignedGossipMessage) string { 434 return string(msg.GetPeerIdentity().PkiId) 435 }, 436 MemSvc: memberSvc, 437 } 438 pullMediator := pull.NewPullMediator(config, adapter) 439 selfIdentity := api.PeerIdentityType("SELF") 440 certStore = newCertStore(&pullerMock{ 441 Mediator: pullMediator, 442 }, identity.NewIdentityMapper(cs, selfIdentity, func(pkiID common.PKIidType, _ api.PeerIdentityType) { 443 pullMediator.Remove(string(pkiID)) 444 }, cs), selfIdentity, cs) 445 446 wg := sync.WaitGroup{} 447 wg.Add(1) 448 sentHello := false 449 sentDataReq := false 450 l := sync.Mutex{} 451 sender.On("Send", mock.Anything, mock.Anything).Run(func(arg mock.Arguments) { 452 msg := arg.Get(0).(*protoext.SignedGossipMessage) 453 l.Lock() 454 defer l.Unlock() 455 456 if hello := msg.GetHello(); hello != nil && !sentHello { 457 sentHello = true 458 go certStore.handleMessage(createDigest(hello.Nonce)) 459 } 460 461 if dataReq := msg.GetDataReq(); dataReq != nil && !sentDataReq { 462 sentDataReq = true 463 certStore.handleMessage(updateFactory(dataReq.Nonce)) 464 wg.Done() 465 } 466 }) 467 wg.Wait() 468 return pullMediator, certStore, sender 469 }