/* Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ package identity import ( "bytes" "errors" "fmt" "strings" "testing" "time" cb "github.com/hyperledger/fabric-protos-go/common" "github.com/hyperledger/fabric/gossip/api" "github.com/hyperledger/fabric/gossip/common" "github.com/hyperledger/fabric/gossip/util" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) var ( msgCryptoService = &naiveCryptoService{revokedIdentities: map[string]struct{}{}} dummyID = api.PeerIdentityType("dummyID") ) type naiveCryptoService struct { mock.Mock revokedIdentities map[string]struct{} } var noopPurgeTrigger = func(_ common.PKIidType, _ api.PeerIdentityType) {} func init() { util.SetupTestLogging() msgCryptoService.On("Expiration", api.PeerIdentityType(dummyID)).Return(time.Now().Add(time.Hour), nil) msgCryptoService.On("Expiration", api.PeerIdentityType("yacovm")).Return(time.Now().Add(time.Hour), nil) msgCryptoService.On("Expiration", api.PeerIdentityType("not-yacovm")).Return(time.Now().Add(time.Hour), nil) msgCryptoService.On("Expiration", api.PeerIdentityType("invalidIdentity")).Return(time.Now().Add(time.Hour), nil) } func (cs *naiveCryptoService) OrgByPeerIdentity(id api.PeerIdentityType) api.OrgIdentityType { found := false for _, call := range cs.Mock.ExpectedCalls { if call.Method == "OrgByPeerIdentity" { found = true } } if !found { return nil } return cs.Called(id).Get(0).(api.OrgIdentityType) } func (cs *naiveCryptoService) Expiration(peerIdentity api.PeerIdentityType) (time.Time, error) { args := cs.Called(peerIdentity) t, err := args.Get(0), args.Get(1) if err == nil { return t.(time.Time), nil } return time.Time{}, err.(error) } func (cs *naiveCryptoService) ValidateIdentity(peerIdentity api.PeerIdentityType) error { if _, isRevoked := cs.revokedIdentities[string(cs.GetPKIidOfCert(peerIdentity))]; isRevoked { return errors.New("revoked") } return nil } // GetPKIidOfCert returns the PKI-ID of a peer's identity func (*naiveCryptoService) GetPKIidOfCert(peerIdentity api.PeerIdentityType) common.PKIidType { return common.PKIidType(peerIdentity) } // VerifyBlock returns nil if the block is properly signed, // else returns error func (*naiveCryptoService) VerifyBlock(channelID common.ChannelID, seqNum uint64, signedBlock *cb.Block) error { return nil } // VerifyBlockAttestation returns nil if the block attestation is properly signed, // else returns error func (*naiveCryptoService) VerifyBlockAttestation(channelID string, signedBlock *cb.Block) error { return nil } // VerifyByChannel verifies a peer's signature on a message in the context // of a specific channel func (*naiveCryptoService) VerifyByChannel(_ common.ChannelID, _ api.PeerIdentityType, _, _ []byte) error { return nil } // Sign signs msg with this peer's signing key and outputs // the signature if no error occurred. func (*naiveCryptoService) Sign(msg []byte) ([]byte, error) { return msg, nil } // Verify checks that signature is a valid signature of message under a peer's verification key. // If the verification succeeded, Verify returns nil meaning no error occurred. // If peerCert is nil, then the signature is verified against this peer's verification key. func (*naiveCryptoService) Verify(peerIdentity api.PeerIdentityType, signature, message []byte) error { equal := bytes.Equal(signature, message) if !equal { return fmt.Errorf("Wrong certificate") } return nil } func TestPut(t *testing.T) { idStore := NewIdentityMapper(msgCryptoService, dummyID, noopPurgeTrigger, msgCryptoService) identity := []byte("yacovm") identity2 := []byte("not-yacovm") identity3 := []byte("invalidIdentity") msgCryptoService.revokedIdentities[string(identity3)] = struct{}{} pkiID := msgCryptoService.GetPKIidOfCert(api.PeerIdentityType(identity)) pkiID2 := msgCryptoService.GetPKIidOfCert(api.PeerIdentityType(identity2)) pkiID3 := msgCryptoService.GetPKIidOfCert(api.PeerIdentityType(identity3)) require.NoError(t, idStore.Put(pkiID, identity)) require.NoError(t, idStore.Put(pkiID, identity)) require.Error(t, idStore.Put(nil, identity)) require.Error(t, idStore.Put(pkiID2, nil)) require.Error(t, idStore.Put(pkiID2, identity)) require.Error(t, idStore.Put(pkiID, identity2)) require.Error(t, idStore.Put(pkiID3, identity3)) } func TestGet(t *testing.T) { idStore := NewIdentityMapper(msgCryptoService, dummyID, noopPurgeTrigger, msgCryptoService) identity := []byte("yacovm") identity2 := []byte("not-yacovm") pkiID := msgCryptoService.GetPKIidOfCert(api.PeerIdentityType(identity)) pkiID2 := msgCryptoService.GetPKIidOfCert(api.PeerIdentityType(identity2)) require.NoError(t, idStore.Put(pkiID, identity)) cert, err := idStore.Get(pkiID) require.NoError(t, err) require.Equal(t, api.PeerIdentityType(identity), cert) cert, err = idStore.Get(pkiID2) require.Nil(t, cert) require.Error(t, err) } func TestVerify(t *testing.T) { idStore := NewIdentityMapper(msgCryptoService, dummyID, noopPurgeTrigger, msgCryptoService) identity := []byte("yacovm") identity2 := []byte("not-yacovm") pkiID := msgCryptoService.GetPKIidOfCert(api.PeerIdentityType(identity)) pkiID2 := msgCryptoService.GetPKIidOfCert(api.PeerIdentityType(identity2)) idStore.Put(pkiID, api.PeerIdentityType(identity)) signed, err := idStore.Sign([]byte("bla bla")) require.NoError(t, err) require.NoError(t, idStore.Verify(pkiID, signed, []byte("bla bla"))) require.Error(t, idStore.Verify(pkiID2, signed, []byte("bla bla"))) } func TestListInvalidIdentities(t *testing.T) { deletedIdentities := make(chan string, 1) assertDeletedIdentity := func(expected string) { select { case <-time.After(time.Second * 10): t.Fatalf("Didn't detect a deleted identity, expected %s to be deleted", expected) case actual := <-deletedIdentities: require.Equal(t, expected, actual) } } // set the time-based expiration time limit to something small SetIdentityUsageThreshold(time.Millisecond * 500) require.Equal(t, time.Millisecond*500, GetIdentityUsageThreshold()) selfPKIID := msgCryptoService.GetPKIidOfCert(dummyID) idStore := NewIdentityMapper(msgCryptoService, dummyID, func(_ common.PKIidType, identity api.PeerIdentityType) { deletedIdentities <- string(identity) }, msgCryptoService) identity := []byte("yacovm") // Test for a revoked identity pkiID := msgCryptoService.GetPKIidOfCert(api.PeerIdentityType(identity)) require.NoError(t, idStore.Put(pkiID, api.PeerIdentityType(identity))) cert, err := idStore.Get(pkiID) require.NoError(t, err) require.NotNil(t, cert) // Revoke the certificate msgCryptoService.revokedIdentities[string(pkiID)] = struct{}{} idStore.SuspectPeers(func(_ api.PeerIdentityType) bool { return true }) // Make sure it is not found anymore cert, err = idStore.Get(pkiID) require.Error(t, err) require.Nil(t, cert) assertDeletedIdentity("yacovm") // Clean the MCS revocation mock msgCryptoService.revokedIdentities = map[string]struct{}{} // Now, test for a certificate that has not been used // for a long time // Add back the identity pkiID = msgCryptoService.GetPKIidOfCert(api.PeerIdentityType(identity)) require.NoError(t, idStore.Put(pkiID, api.PeerIdentityType(identity))) // Check it exists in the meantime cert, err = idStore.Get(pkiID) require.NoError(t, err) require.NotNil(t, cert) time.Sleep(time.Second * 3) // Make sure it has expired cert, err = idStore.Get(pkiID) require.Error(t, err) require.Nil(t, cert) assertDeletedIdentity("yacovm") // Make sure our own identity hasn't been expired _, err = idStore.Get(selfPKIID) require.NoError(t, err) // Now test that an identity that is frequently used doesn't expire // Add back the identity pkiID = msgCryptoService.GetPKIidOfCert(api.PeerIdentityType(identity)) require.NoError(t, idStore.Put(pkiID, api.PeerIdentityType(identity))) stopChan := make(chan struct{}) go func() { for { select { case <-stopChan: return case <-time.After(time.Millisecond * 10): idStore.Get(pkiID) } } }() time.Sleep(time.Second * 3) // Ensure it hasn't expired even though time has passed cert, err = idStore.Get(pkiID) require.NoError(t, err) require.NotNil(t, cert) stopChan <- struct{}{} // Stop the identity store - this would make periodical un-usage // expiration stop idStore.Stop() time.Sleep(time.Second * 3) // Ensure it hasn't expired even though time has passed cert, err = idStore.Get(pkiID) require.NoError(t, err) require.NotNil(t, cert) } func TestExpiration(t *testing.T) { deletedIdentities := make(chan string, 1) SetIdentityUsageThreshold(time.Second * 500) idStore := NewIdentityMapper(msgCryptoService, dummyID, func(_ common.PKIidType, identity api.PeerIdentityType) { deletedIdentities <- string(identity) }, msgCryptoService) assertDeletedIdentity := func(expected string) { select { case <-time.After(time.Second * 10): t.Fatalf("Didn't detect a deleted identity, expected %s to be deleted", expected) case actual := <-deletedIdentities: require.Equal(t, expected, actual) } } x509Identity := api.PeerIdentityType("x509Identity") expiredX509Identity := api.PeerIdentityType("expiredX509Identity") nonX509Identity := api.PeerIdentityType("nonX509Identity") notSupportedIdentity := api.PeerIdentityType("notSupportedIdentity") x509PkiID := idStore.GetPKIidOfCert(x509Identity) expiredX509PkiID := idStore.GetPKIidOfCert(expiredX509Identity) nonX509PkiID := idStore.GetPKIidOfCert(nonX509Identity) notSupportedPkiID := idStore.GetPKIidOfCert(notSupportedIdentity) msgCryptoService.On("Expiration", x509Identity).Return(time.Now().Add(time.Second), nil) msgCryptoService.On("Expiration", expiredX509Identity).Return(time.Now().Add(-time.Second), nil) msgCryptoService.On("Expiration", nonX509Identity).Return(time.Time{}, nil) msgCryptoService.On("Expiration", notSupportedIdentity).Return(time.Time{}, errors.New("no MSP supports given identity")) // Add all identities err := idStore.Put(x509PkiID, x509Identity) require.NoError(t, err) err = idStore.Put(expiredX509PkiID, expiredX509Identity) require.Equal(t, "gossipping peer identity expired", err.Error()) err = idStore.Put(nonX509PkiID, nonX509Identity) require.NoError(t, err) err = idStore.Put(notSupportedPkiID, notSupportedIdentity) require.Error(t, err) require.Contains(t, err.Error(), "no MSP supports given identity") // Make sure the x509 cert and the non x509 cert exist in the store returnedIdentity, err := idStore.Get(x509PkiID) require.NoError(t, err) require.NotEmpty(t, returnedIdentity) returnedIdentity, err = idStore.Get(nonX509PkiID) require.NoError(t, err) require.NotEmpty(t, returnedIdentity) // Wait for the x509 identity to expire time.Sleep(time.Second * 3) // Ensure only the non x509 identity exists now returnedIdentity, err = idStore.Get(x509PkiID) require.Error(t, err) require.Contains(t, err.Error(), "PKIID wasn't found") require.Empty(t, returnedIdentity) assertDeletedIdentity("x509Identity") returnedIdentity, err = idStore.Get(nonX509PkiID) require.NoError(t, err) require.NotEmpty(t, returnedIdentity) // Ensure that when it is revoked, an expiration timer isn't cancelled for it msgCryptoService.revokedIdentities[string(nonX509PkiID)] = struct{}{} idStore.SuspectPeers(func(_ api.PeerIdentityType) bool { return true }) assertDeletedIdentity("nonX509Identity") msgCryptoService.revokedIdentities = map[string]struct{}{} } func TestExpirationPanic(t *testing.T) { identity3 := []byte("invalidIdentity") msgCryptoService.revokedIdentities[string(identity3)] = struct{}{} require.Panics(t, func() { NewIdentityMapper(msgCryptoService, identity3, noopPurgeTrigger, msgCryptoService) }) } func TestIdentityInfo(t *testing.T) { cs := &naiveCryptoService{} alice := api.PeerIdentityType("alicePeer") bob := api.PeerIdentityType("bobPeer") aliceID := cs.GetPKIidOfCert(alice) bobId := cs.GetPKIidOfCert(bob) cs.On("OrgByPeerIdentity", dummyID).Return(api.OrgIdentityType("D")) cs.On("OrgByPeerIdentity", alice).Return(api.OrgIdentityType("A")) cs.On("OrgByPeerIdentity", bob).Return(api.OrgIdentityType("B")) cs.On("Expiration", mock.Anything).Return(time.Now().Add(time.Minute), nil) idStore := NewIdentityMapper(cs, dummyID, noopPurgeTrigger, cs) idStore.Put(aliceID, alice) idStore.Put(bobId, bob) for org, id := range idStore.IdentityInfo().ByOrg() { identity := string(id[0].Identity) pkiID := string(id[0].PKIId) orgId := string(id[0].Organization) require.Equal(t, org, orgId) require.Equal(t, strings.ToLower(org), string(identity[0])) require.Equal(t, strings.ToLower(org), string(pkiID[0])) } }