Skip to content

Commit 08a2515

Browse files
committed
[FAB-4085] Prevent expiration of self identity
In gossip we have time-based expiration (purge) of identities that haven't been used for a long time. This may cause an expiration of the peer's own certificate if it doesn't gossip with any peers (i.e if it's alone in the world). The problem is that when an identity expires from the identity store, it is also expired from the pull mediator that is used for identities. This is important because the only way identities are gossiped transitively is via the pull mechanism. If a peer's own identity disappears from the pull mediator, it will never be sent to peers transitively. Change-Id: Ic2138ebaa60bdf2454d65d3884f141c3736254a0 Signed-off-by: Yacov Manevich <[email protected]>
1 parent ee77584 commit 08a2515

12 files changed

+124
-43
lines changed

gossip/comm/comm_impl.go

-3
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,6 @@ func NewCommInstanceWithServer(port int, idMapper identity.Mapper, peerIdentity
9898
subscriptions: make([]chan proto.ReceivedMessage, 0),
9999
}
100100
commInst.connStore = newConnStore(commInst, commInst.logger)
101-
if err := commInst.idMapper.Put(idMapper.GetPKIidOfCert(peerIdentity), peerIdentity); err != nil {
102-
commInst.logger.Panic("Failed associating self PKIID to cert:", err)
103-
}
104101

105102
if port > 0 {
106103
commInst.stopWG.Add(1)

gossip/comm/comm_test.go

+6-3
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ func (*naiveSecProvider) VerifyByChannel(_ common.ChainID, _ api.PeerIdentityTyp
105105

106106
func newCommInstance(port int, sec api.MessageCryptoService) (Comm, error) {
107107
endpoint := fmt.Sprintf("localhost:%d", port)
108-
inst, err := NewCommInstanceWithServer(port, identity.NewIdentityMapper(sec), []byte(endpoint), nil)
108+
id := []byte(endpoint)
109+
inst, err := NewCommInstanceWithServer(port, identity.NewIdentityMapper(sec, id), id, nil)
109110
return inst, err
110111
}
111112

@@ -285,15 +286,17 @@ func TestProdConstructor(t *testing.T) {
285286
srv, lsnr, dialOpts, certHash := createGRPCLayer(20000)
286287
defer srv.Stop()
287288
defer lsnr.Close()
288-
comm1, _ := NewCommInstance(srv, &peerIdentity, identity.NewIdentityMapper(naiveSec), []byte("localhost:20000"), dialOpts)
289+
id := []byte("localhost:20000")
290+
comm1, _ := NewCommInstance(srv, &peerIdentity, identity.NewIdentityMapper(naiveSec, id), id, dialOpts)
289291
comm1.(*commImpl).selfCertHash = certHash
290292
go srv.Serve(lsnr)
291293

292294
peerIdentity = GenerateCertificatesOrPanic()
293295
srv, lsnr, dialOpts, certHash = createGRPCLayer(30000)
294296
defer srv.Stop()
295297
defer lsnr.Close()
296-
comm2, _ := NewCommInstance(srv, &peerIdentity, identity.NewIdentityMapper(naiveSec), []byte("localhost:30000"), dialOpts)
298+
id = []byte("localhost:30000")
299+
comm2, _ := NewCommInstance(srv, &peerIdentity, identity.NewIdentityMapper(naiveSec, id), id, dialOpts)
297300
comm2.(*commImpl).selfCertHash = certHash
298301
go srv.Serve(lsnr)
299302
defer comm1.Stop()

gossip/gossip/certstore.go

-4
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,6 @@ type certStore struct {
4343
func newCertStore(puller pull.Mediator, idMapper identity.Mapper, selfIdentity api.PeerIdentityType, mcs api.MessageCryptoService) *certStore {
4444
selfPKIID := idMapper.GetPKIidOfCert(selfIdentity)
4545
logger := util.GetLogger(util.LoggingGossipModule, string(selfPKIID))
46-
if err := idMapper.Put(selfPKIID, selfIdentity); err != nil {
47-
logger.Error("Failed associating self PKIID to cert:", err)
48-
panic(fmt.Errorf("Failed associating self PKIID to cert: %v", err))
49-
}
5046

5147
certStore := &certStore{
5248
mcs: mcs,

gossip/gossip/certstore_test.go

+64-2
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ func TestCertStoreShouldSucceed(t *testing.T) {
122122
testCertificateUpdate(t, true, cs)
123123
}
124124

125-
func TestCertExpiration(t *testing.T) {
125+
func TestCertRevocation(t *testing.T) {
126126
identityExpCheckInterval := identityExpirationCheckInterval
127127
defer func() {
128128
identityExpirationCheckInterval = identityExpCheckInterval
@@ -225,6 +225,67 @@ func TestCertExpiration(t *testing.T) {
225225
}
226226
}
227227

228+
func TestCertExpiration(t *testing.T) {
229+
// Scenario: In this test we make sure that a peer may not expire
230+
// its own identity.
231+
// This is important because the only way identities are gossiped
232+
// transitively is via the pull mechanism.
233+
// If a peer's own identity disappears from the pull mediator,
234+
// it will never be sent to peers transitively.
235+
// The test ensures that self identities don't expire
236+
// in the following manner:
237+
// It starts a peer and then sleeps twice the identity usage threshold,
238+
// in order to make sure that its own identity should be expired.
239+
// Then, it starts another peer, and listens to the messages sent
240+
// between both peers, and looks for a few identity digests of the first peer.
241+
// If such identity digest are detected, it means that the peer
242+
// didn't expire its own identity.
243+
244+
// Backup original usageThreshold value
245+
idUsageThreshold := identity.GetIdentityUsageThreshold()
246+
identity.SetIdentityUsageThreshold(time.Second)
247+
// Restore original usageThreshold value
248+
defer identity.SetIdentityUsageThreshold(idUsageThreshold)
249+
250+
// Backup original identityInactivityCheckInterval value
251+
inactivityCheckInterval := identityInactivityCheckInterval
252+
identityInactivityCheckInterval = time.Second * 1
253+
// Restore original identityInactivityCheckInterval value
254+
defer func() {
255+
identityInactivityCheckInterval = inactivityCheckInterval
256+
}()
257+
258+
g1 := newGossipInstance(4321, 0, 0, 1)
259+
defer g1.Stop()
260+
time.Sleep(identity.GetIdentityUsageThreshold() * 2)
261+
g2 := newGossipInstance(4322, 0, 0)
262+
defer g2.Stop()
263+
264+
identities2Detect := 3
265+
// Make the channel bigger than needed so goroutines won't get stuck
266+
identitiesGotViaPull := make(chan struct{}, identities2Detect+100)
267+
acceptIdentityPullMsgs := func(o interface{}) bool {
268+
m := o.(proto.ReceivedMessage).GetGossipMessage()
269+
if m.IsPullMsg() && m.IsDigestMsg() {
270+
for _, dig := range m.GetDataDig().Digests {
271+
if dig == "localhost:4321" {
272+
identitiesGotViaPull <- struct{}{}
273+
}
274+
}
275+
}
276+
return false
277+
}
278+
g1.Accept(acceptIdentityPullMsgs, true)
279+
for i := 0; i < identities2Detect; i++ {
280+
select {
281+
case <-identitiesGotViaPull:
282+
case <-time.After(time.Second * 15):
283+
assert.Fail(t, "Didn't detect an identity gossiped via pull in a timely manner")
284+
return
285+
}
286+
}
287+
}
288+
228289
func testCertificateUpdate(t *testing.T, shouldSucceed bool, certStore *certStore) {
229290
hello := &sentMsg{
230291
msg: (&proto.GossipMessage{
@@ -396,9 +457,10 @@ func createObjects(updateFactory func(uint64) proto.ReceivedMessage, msgCons pro
396457
MemSvc: memberSvc,
397458
}
398459
pullMediator := pull.NewPullMediator(config, adapter)
460+
selfIdentity := api.PeerIdentityType("SELF")
399461
certStore = newCertStore(&pullerMock{
400462
Mediator: pullMediator,
401-
}, identity.NewIdentityMapper(cs), api.PeerIdentityType("SELF"), cs)
463+
}, identity.NewIdentityMapper(cs, selfIdentity), selfIdentity, cs)
402464

403465
wg := sync.WaitGroup{}
404466
wg.Add(1)

gossip/gossip/gossip_test.go

+6-5
Original file line numberDiff line numberDiff line change
@@ -218,10 +218,10 @@ func newGossipInstanceWithCustomMCS(portPrefix int, id int, maxMsgCount int, mcs
218218
PublishStateInfoInterval: time.Duration(1) * time.Second,
219219
RequestStateInfoInterval: time.Duration(1) * time.Second,
220220
}
221-
222-
idMapper := identity.NewIdentityMapper(mcs)
221+
selfId := api.PeerIdentityType(conf.InternalEndpoint)
222+
idMapper := identity.NewIdentityMapper(mcs, selfId)
223223
g := NewGossipServiceWithServer(conf, &orgCryptoService{}, mcs, idMapper,
224-
api.PeerIdentityType(conf.InternalEndpoint), nil)
224+
selfId, nil)
225225

226226
return g
227227
}
@@ -251,10 +251,11 @@ func newGossipInstanceWithOnlyPull(portPrefix int, id int, maxMsgCount int, boot
251251
}
252252

253253
cryptoService := &naiveCryptoService{}
254-
idMapper := identity.NewIdentityMapper(cryptoService)
254+
selfId := api.PeerIdentityType(conf.InternalEndpoint)
255+
idMapper := identity.NewIdentityMapper(cryptoService, selfId)
255256

256257
g := NewGossipServiceWithServer(conf, &orgCryptoService{}, cryptoService, idMapper,
257-
api.PeerIdentityType(conf.InternalEndpoint), nil)
258+
selfId, nil)
258259
return g
259260
}
260261

gossip/gossip/orgs_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,9 @@ func newGossipInstanceWithExternalEndpoint(portPrefix int, id int, mcs *configur
119119
PublishStateInfoInterval: time.Duration(1) * time.Second,
120120
RequestStateInfoInterval: time.Duration(1) * time.Second,
121121
}
122-
123-
idMapper := identity.NewIdentityMapper(mcs)
124-
g := NewGossipServiceWithServer(conf, mcs, mcs, idMapper, api.PeerIdentityType(conf.InternalEndpoint),
122+
selfId := api.PeerIdentityType(conf.InternalEndpoint)
123+
idMapper := identity.NewIdentityMapper(mcs, selfId)
124+
g := NewGossipServiceWithServer(conf, mcs, mcs, idMapper, selfId,
125125
nil)
126126

127127
return g

gossip/identity/identity.go

+27-6
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ package identity
1919
import (
2020
"bytes"
2121
"errors"
22+
"fmt"
2223
"sync"
23-
"time"
24-
2524
"sync/atomic"
25+
"time"
2626

2727
"github.com/hyperledger/fabric/gossip/api"
2828
"github.com/hyperledger/fabric/gossip/common"
@@ -31,7 +31,7 @@ import (
3131
var (
3232
// identityUsageThreshold sets the maximum time that an identity
3333
// can not be used to verify some signature before it will be deleted
34-
identityUsageThreshold = time.Hour
34+
usageThreshold = time.Hour
3535
)
3636

3737
// Mapper holds mappings between pkiID
@@ -66,14 +66,21 @@ type identityMapperImpl struct {
6666
mcs api.MessageCryptoService
6767
pkiID2Cert map[string]*storedIdentity
6868
sync.RWMutex
69+
selfPKIID string
6970
}
7071

7172
// NewIdentityMapper method, all we need is a reference to a MessageCryptoService
72-
func NewIdentityMapper(mcs api.MessageCryptoService) Mapper {
73-
return &identityMapperImpl{
73+
func NewIdentityMapper(mcs api.MessageCryptoService, selfIdentity api.PeerIdentityType) Mapper {
74+
selfPKIID := mcs.GetPKIidOfCert(selfIdentity)
75+
idMapper := &identityMapperImpl{
7476
mcs: mcs,
7577
pkiID2Cert: make(map[string]*storedIdentity),
78+
selfPKIID: string(selfPKIID),
79+
}
80+
if err := idMapper.Put(selfPKIID, selfIdentity); err != nil {
81+
panic(fmt.Errorf("Failed putting our own identity into the identity mapper: %v", err))
7682
}
83+
return idMapper
7784
}
7885

7986
// put associates an identity to its given pkiID, and returns an error
@@ -157,7 +164,7 @@ func (is *identityMapperImpl) validateIdentities(isSuspected api.PeerSuspector)
157164
defer is.RUnlock()
158165
var revokedIds []common.PKIidType
159166
for pkiID, storedIdentity := range is.pkiID2Cert {
160-
if storedIdentity.fetchLastAccessTime().Add(identityUsageThreshold).Before(now) {
167+
if pkiID != is.selfPKIID && storedIdentity.fetchLastAccessTime().Add(usageThreshold).Before(now) {
161168
revokedIds = append(revokedIds, common.PKIidType(pkiID))
162169
continue
163170
}
@@ -191,3 +198,17 @@ func (si *storedIdentity) fetchIdentity() api.PeerIdentityType {
191198
func (si *storedIdentity) fetchLastAccessTime() time.Time {
192199
return time.Unix(0, atomic.LoadInt64(&si.lastAccessTime))
193200
}
201+
202+
// SetIdentityUsageThreshold sets the usage threshold of identities.
203+
// Identities that are not used at least once during the given time
204+
// are purged
205+
func SetIdentityUsageThreshold(duration time.Duration) {
206+
usageThreshold = duration
207+
}
208+
209+
// GetIdentityUsageThreshold returns the usage threshold of identities.
210+
// Identities that are not used at least once during the usage threshold
211+
// duration are purged.
212+
func GetIdentityUsageThreshold() time.Duration {
213+
return usageThreshold
214+
}

gossip/identity/identity_test.go

+9-6
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ import (
3030
"github.com/stretchr/testify/assert"
3131
)
3232

33-
var msgCryptoService = &naiveCryptoService{revokedIdentities: map[string]struct{}{}}
33+
var (
34+
msgCryptoService = &naiveCryptoService{revokedIdentities: map[string]struct{}{}}
35+
dummyID = api.PeerIdentityType{}
36+
)
3437

3538
type naiveCryptoService struct {
3639
revokedIdentities map[string]struct{}
@@ -82,7 +85,7 @@ func (*naiveCryptoService) Verify(peerIdentity api.PeerIdentityType, signature,
8285
}
8386

8487
func TestPut(t *testing.T) {
85-
idStore := NewIdentityMapper(msgCryptoService)
88+
idStore := NewIdentityMapper(msgCryptoService, dummyID)
8689
identity := []byte("yacovm")
8790
identity2 := []byte("not-yacovm")
8891
pkiID := msgCryptoService.GetPKIidOfCert(api.PeerIdentityType(identity))
@@ -95,7 +98,7 @@ func TestPut(t *testing.T) {
9598
}
9699

97100
func TestGet(t *testing.T) {
98-
idStore := NewIdentityMapper(msgCryptoService)
101+
idStore := NewIdentityMapper(msgCryptoService, dummyID)
99102
identity := []byte("yacovm")
100103
identity2 := []byte("not-yacovm")
101104
pkiID := msgCryptoService.GetPKIidOfCert(api.PeerIdentityType(identity))
@@ -110,7 +113,7 @@ func TestGet(t *testing.T) {
110113
}
111114

112115
func TestVerify(t *testing.T) {
113-
idStore := NewIdentityMapper(msgCryptoService)
116+
idStore := NewIdentityMapper(msgCryptoService, dummyID)
114117
identity := []byte("yacovm")
115118
identity2 := []byte("not-yacovm")
116119
pkiID := msgCryptoService.GetPKIidOfCert(api.PeerIdentityType(identity))
@@ -123,7 +126,7 @@ func TestVerify(t *testing.T) {
123126
}
124127

125128
func TestListInvalidIdentities(t *testing.T) {
126-
idStore := NewIdentityMapper(msgCryptoService)
129+
idStore := NewIdentityMapper(msgCryptoService, dummyID)
127130
identity := []byte("yacovm")
128131
// Test for a revoked identity
129132
pkiID := msgCryptoService.GetPKIidOfCert(api.PeerIdentityType(identity))
@@ -150,7 +153,7 @@ func TestListInvalidIdentities(t *testing.T) {
150153
pkiID = msgCryptoService.GetPKIidOfCert(api.PeerIdentityType(identity))
151154
assert.NoError(t, idStore.Put(pkiID, api.PeerIdentityType(identity)))
152155
// set the time-based expiration time limit to something small
153-
identityUsageThreshold = time.Millisecond * 500
156+
usageThreshold = time.Millisecond * 500
154157
idStore.ListInvalidIdentities(func(_ api.PeerIdentityType) bool {
155158
return false
156159
})

gossip/integration/integration_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ func TestNewGossipCryptoService(t *testing.T) {
6262
endpoint3 := "localhost:5613"
6363
msptesttools.LoadMSPSetupForTesting()
6464
peerIdentity, _ := mgmt.GetLocalSigningIdentityOrPanic().Serialize()
65-
idMapper := identity.NewIdentityMapper(cryptSvc)
65+
idMapper := identity.NewIdentityMapper(cryptSvc, peerIdentity)
6666

6767
g1 := NewGossipComponent(peerIdentity, endpoint1, s1, secAdv, cryptSvc, idMapper,
6868
defaultSecureDialOpts)
@@ -82,7 +82,7 @@ func TestBadInitialization(t *testing.T) {
8282
msptesttools.LoadMSPSetupForTesting()
8383
peerIdentity, _ := mgmt.GetLocalSigningIdentityOrPanic().Serialize()
8484
s1 := grpc.NewServer()
85-
idMapper := identity.NewIdentityMapper(cryptSvc)
85+
idMapper := identity.NewIdentityMapper(cryptSvc, peerIdentity)
8686
assert.Panics(t, func() {
8787
newConfig("anEndpointWithoutAPort", "anEndpointWithoutAPort")
8888
})

gossip/service/gossip_service.go

+1-5
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,7 @@ func InitGossipServiceCustomDeliveryFactory(peerIdentity []byte, endpoint string
141141

142142
logger.Info("Initialize gossip with endpoint", endpoint, "and bootstrap set", bootPeers)
143143

144-
idMapper := identity.NewIdentityMapper(mcs)
145-
if err := idMapper.Put(mcs.GetPKIidOfCert(peerIdentity), peerIdentity); err != nil {
146-
logger.Panic("Failed associating self PKIID to cert:", err)
147-
}
148-
144+
idMapper := identity.NewIdentityMapper(mcs, peerIdentity)
149145
gossip := integration.NewGossipComponent(peerIdentity, endpoint, s, secAdv,
150146
mcs, idMapper, secureDialOpts, bootPeers...)
151147
gossipServiceInstance = &gossipServiceImpl{

gossip/service/gossip_service_test.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -612,11 +612,12 @@ func newGossipInstance(portPrefix int, id int, maxMsgCount int, boot ...int) Gos
612612
PublishStateInfoInterval: time.Duration(1) * time.Second,
613613
RequestStateInfoInterval: time.Duration(1) * time.Second,
614614
}
615+
selfId := api.PeerIdentityType(conf.InternalEndpoint)
615616
cryptoService := &naiveCryptoService{}
616-
idMapper := identity.NewIdentityMapper(cryptoService)
617+
idMapper := identity.NewIdentityMapper(cryptoService, selfId)
617618

618619
gossip := gossip.NewGossipServiceWithServer(conf, &orgCryptoService{}, cryptoService,
619-
idMapper, api.PeerIdentityType(conf.InternalEndpoint), nil)
620+
idMapper, selfId, nil)
620621

621622
gossipService := &gossipServiceImpl{
622623
gossipSvc: gossip,

gossip/state/state_test.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -214,9 +214,10 @@ func newGossipConfig(id int, boot ...int) *gossip.Config {
214214

215215
// Create gossip instance
216216
func newGossipInstance(config *gossip.Config, mcs api.MessageCryptoService) gossip.Gossip {
217-
idMapper := identity.NewIdentityMapper(mcs)
217+
id := api.PeerIdentityType(config.InternalEndpoint)
218+
idMapper := identity.NewIdentityMapper(mcs, id)
218219
return gossip.NewGossipServiceWithServer(config, &orgCryptoService{}, mcs,
219-
idMapper, []byte(config.InternalEndpoint), nil)
220+
idMapper, id, nil)
220221
}
221222

222223
// Create new instance of KVLedger to be used for testing

0 commit comments

Comments
 (0)