346 lines
12 KiB
Go
346 lines
12 KiB
Go
/*
|
|
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]))
|
|
}
|
|
}
|