diff --git a/core/common/validation/fullflow_test.go b/core/common/validation/fullflow_test.go index a6f03b50003..3dd5dbf5547 100644 --- a/core/common/validation/fullflow_test.go +++ b/core/common/validation/fullflow_test.go @@ -35,13 +35,13 @@ import ( "github.com/stretchr/testify/assert" ) -func getProposal() (*peer.Proposal, error) { +func getProposal(channel string) (*peer.Proposal, error) { cis := &peer.ChaincodeInvocationSpec{ ChaincodeSpec: &peer.ChaincodeSpec{ ChaincodeId: getChaincodeID(), Type: peer.ChaincodeSpec_GOLANG}} - proposal, _, err := utils.CreateProposalFromCIS(common.HeaderType_ENDORSER_TRANSACTION, util.GetTestChainID(), cis, signerSerialized) + proposal, _, err := utils.CreateProposalFromCIS(common.HeaderType_ENDORSER_TRANSACTION, channel, cis, signerSerialized) return proposal, err } @@ -120,7 +120,7 @@ func createSignedTxTwoActions(proposal *peer.Proposal, signer msp.SigningIdentit func TestGoodPath(t *testing.T) { // get a toy proposal - prop, err := getProposal() + prop, err := getProposal(util.GetTestChainID()) if err != nil { t.Fatalf("getProposal failed, err %s", err) return @@ -194,7 +194,7 @@ func TestGoodPath(t *testing.T) { func TestTXWithTwoActionsRejected(t *testing.T) { // get a toy proposal - prop, err := getProposal() + prop, err := getProposal(util.GetTestChainID()) if err != nil { t.Fatalf("getProposal failed, err %s", err) return @@ -227,7 +227,7 @@ func TestTXWithTwoActionsRejected(t *testing.T) { func TestBadProp(t *testing.T) { // get a toy proposal - prop, err := getProposal() + prop, err := getProposal(util.GetTestChainID()) if err != nil { t.Fatalf("getProposal failed, err %s", err) return @@ -304,7 +304,7 @@ func corrupt(bytes []byte) { func TestBadTx(t *testing.T) { // get a toy proposal - prop, err := getProposal() + prop, err := getProposal(util.GetTestChainID()) if err != nil { t.Fatalf("getProposal failed, err %s", err) return @@ -361,7 +361,7 @@ func TestBadTx(t *testing.T) { func Test2EndorsersAgree(t *testing.T) { // get a toy proposal - prop, err := getProposal() + prop, err := getProposal(util.GetTestChainID()) if err != nil { t.Fatalf("getProposal failed, err %s", err) return @@ -404,7 +404,7 @@ func Test2EndorsersAgree(t *testing.T) { func Test2EndorsersDisagree(t *testing.T) { // get a toy proposal - prop, err := getProposal() + prop, err := getProposal(util.GetTestChainID()) if err != nil { t.Fatalf("getProposal failed, err %s", err) return @@ -469,6 +469,7 @@ func TestInvocationsBadArgs(t *testing.T) { var signer msp.SigningIdentity var signerSerialized []byte +var signerMSPId string func TestMain(m *testing.M) { // setup crypto algorithms @@ -486,6 +487,7 @@ func TestMain(m *testing.M) { os.Exit(-1) return } + signerMSPId = signer.GetMSPIdentifier() signerSerialized, err = signer.Serialize() if err != nil { diff --git a/core/common/validation/msgvalidation.go b/core/common/validation/msgvalidation.go index 70496f9abe0..145a708728d 100644 --- a/core/common/validation/msgvalidation.go +++ b/core/common/validation/msgvalidation.go @@ -18,15 +18,16 @@ package validation import ( "bytes" - "errors" - "fmt" + "github.com/golang/protobuf/proto" "github.com/hyperledger/fabric/common/channelconfig" "github.com/hyperledger/fabric/common/flogging" mspmgmt "github.com/hyperledger/fabric/msp/mgmt" "github.com/hyperledger/fabric/protos/common" + "github.com/hyperledger/fabric/protos/msp" pb "github.com/hyperledger/fabric/protos/peer" "github.com/hyperledger/fabric/protos/utils" + "github.com/pkg/errors" ) var putilsLogger = flogging.MustGetLogger("protoutils") @@ -34,7 +35,7 @@ var putilsLogger = flogging.MustGetLogger("protoutils") // validateChaincodeProposalMessage checks the validity of a Proposal message of type CHAINCODE func validateChaincodeProposalMessage(prop *pb.Proposal, hdr *common.Header) (*pb.ChaincodeHeaderExtension, error) { if prop == nil || hdr == nil { - return nil, errors.New("Nil arguments") + return nil, errors.New("nil arguments") } putilsLogger.Debugf("validateChaincodeProposalMessage starts for proposal %p, header %p", prop, hdr) @@ -42,7 +43,7 @@ func validateChaincodeProposalMessage(prop *pb.Proposal, hdr *common.Header) (*p // 4) based on the header type (assuming it's CHAINCODE), look at the extensions chaincodeHdrExt, err := utils.GetChaincodeHeaderExtension(hdr) if err != nil { - return nil, errors.New("Invalid header extension for type CHAINCODE") + return nil, errors.New("invalid header extension for type CHAINCODE") } if chaincodeHdrExt.ChaincodeId == nil { @@ -63,7 +64,7 @@ func validateChaincodeProposalMessage(prop *pb.Proposal, hdr *common.Header) (*p // encode more elaborate visibility mechanisms that shall be encoded in // this field (and handled appropriately by the peer) if chaincodeHdrExt.PayloadVisibility != nil { - return nil, errors.New("Invalid payload visibility field") + return nil, errors.New("invalid payload visibility field") } return chaincodeHdrExt, nil @@ -74,7 +75,7 @@ func validateChaincodeProposalMessage(prop *pb.Proposal, hdr *common.Header) (*p // have been unmarshalled and validated func ValidateProposalMessage(signedProp *pb.SignedProposal) (*pb.Proposal, *common.Header, *pb.ChaincodeHeaderExtension, error) { if signedProp == nil { - return nil, nil, nil, errors.New("Nil arguments") + return nil, nil, nil, errors.New("nil arguments") } putilsLogger.Debugf("ValidateProposalMessage starts for signed proposal %p", signedProp) @@ -100,7 +101,17 @@ func ValidateProposalMessage(signedProp *pb.SignedProposal) (*pb.Proposal, *comm // validate the signature err = checkSignatureFromCreator(shdr.Creator, signedProp.Signature, signedProp.ProposalBytes, chdr.ChannelId) if err != nil { - return nil, nil, nil, err + // log the exact message on the peer but return a generic error message to + // avoid malicious users scanning for channels + putilsLogger.Warningf("channel [%s]: %s", chdr.ChannelId, err) + sId := &msp.SerializedIdentity{} + err := proto.Unmarshal(shdr.Creator, sId) + if err != nil { + // log the error here as well but still only return the generic error + err = errors.Wrap(err, "could not deserialize a SerializedIdentity") + putilsLogger.Warningf("channel [%s]: %s", chdr.ChannelId, err) + } + return nil, nil, nil, errors.Errorf("access denied: channel [%s] creator org [%s]", chdr.ChannelId, sId.Mspid) } // Verify that the transaction ID has been computed properly. @@ -135,7 +146,7 @@ func ValidateProposalMessage(signedProp *pb.SignedProposal) (*pb.Proposal, *comm return prop, hdr, chaincodeHdrExt, err default: //NOTE : we proably need a case - return nil, nil, nil, fmt.Errorf("Unsupported proposal type %d", common.HeaderType(chdr.Type)) + return nil, nil, nil, errors.Errorf("unsupported proposal type %d", common.HeaderType(chdr.Type)) } } @@ -143,41 +154,41 @@ func ValidateProposalMessage(signedProp *pb.SignedProposal) (*pb.Proposal, *comm // this function returns nil if the creator // is a valid cert and the signature is valid func checkSignatureFromCreator(creatorBytes []byte, sig []byte, msg []byte, ChainID string) error { - putilsLogger.Debugf("checkSignatureFromCreator starts") + putilsLogger.Debugf("begin") // check for nil argument if creatorBytes == nil || sig == nil || msg == nil { - return errors.New("Nil arguments") + return errors.New("nil arguments") } mspObj := mspmgmt.GetIdentityDeserializer(ChainID) if mspObj == nil { - return fmt.Errorf("could not get msp for chain [%s]", ChainID) + return errors.Errorf("could not get msp for channel [%s]", ChainID) } // get the identity of the creator creator, err := mspObj.DeserializeIdentity(creatorBytes) if err != nil { - return fmt.Errorf("Failed to deserialize creator identity, err %s", err) + return errors.WithMessage(err, "MSP error") } - putilsLogger.Debugf("checkSignatureFromCreator info: creator is %s", creator.GetIdentifier()) + putilsLogger.Debugf("creator is %s", creator.GetIdentifier()) // ensure that creator is a valid certificate err = creator.Validate() if err != nil { - return fmt.Errorf("The creator certificate is not valid, err %s", err) + return errors.WithMessage(err, "creator certificate is not valid") } - putilsLogger.Debugf("checkSignatureFromCreator info: creator is valid") + putilsLogger.Debugf("creator is valid") // validate the signature err = creator.Verify(msg, sig) if err != nil { - return fmt.Errorf("The creator's signature over the proposal is not valid, err %s", err) + return errors.WithMessage(err, "creator's signature over the proposal is not valid") } - putilsLogger.Debugf("checkSignatureFromCreator exists successfully") + putilsLogger.Debugf("exits successfully") return nil } @@ -186,17 +197,17 @@ func checkSignatureFromCreator(creatorBytes []byte, sig []byte, msg []byte, Chai func validateSignatureHeader(sHdr *common.SignatureHeader) error { // check for nil argument if sHdr == nil { - return errors.New("Nil SignatureHeader provided") + return errors.New("nil SignatureHeader provided") } // ensure that there is a nonce if sHdr.Nonce == nil || len(sHdr.Nonce) == 0 { - return errors.New("Invalid nonce specified in the header") + return errors.New("invalid nonce specified in the header") } // ensure that there is a creator if sHdr.Creator == nil || len(sHdr.Creator) == 0 { - return errors.New("Invalid creator specified in the header") + return errors.New("invalid creator specified in the header") } return nil @@ -206,7 +217,7 @@ func validateSignatureHeader(sHdr *common.SignatureHeader) error { func validateChannelHeader(cHdr *common.ChannelHeader) error { // check for nil argument if cHdr == nil { - return errors.New("Nil ChannelHeader provided") + return errors.New("nil ChannelHeader provided") } // validate the header type @@ -214,7 +225,7 @@ func validateChannelHeader(cHdr *common.ChannelHeader) error { common.HeaderType(cHdr.Type) != common.HeaderType_CONFIG_UPDATE && common.HeaderType(cHdr.Type) != common.HeaderType_CONFIG && common.HeaderType(cHdr.Type) != common.HeaderType_PEER_RESOURCE_UPDATE { - return fmt.Errorf("invalid header type %s", common.HeaderType(cHdr.Type)) + return errors.Errorf("invalid header type %s", common.HeaderType(cHdr.Type)) } putilsLogger.Debugf("validateChannelHeader info: header type %d", common.HeaderType(cHdr.Type)) @@ -226,7 +237,7 @@ func validateChannelHeader(cHdr *common.ChannelHeader) error { // TODO: This check will be modified once the Epoch management // will be in place. if cHdr.Epoch != 0 { - return fmt.Errorf("Invalid Epoch in ChannelHeader. It must be 0. It was [%d]", cHdr.Epoch) + return errors.Errorf("invalid Epoch in ChannelHeader. Expected 0, got [%d]", cHdr.Epoch) } // TODO: Validate version in cHdr.Version @@ -237,7 +248,7 @@ func validateChannelHeader(cHdr *common.ChannelHeader) error { // checks for a valid Header func validateCommonHeader(hdr *common.Header) (*common.ChannelHeader, *common.SignatureHeader, error) { if hdr == nil { - return nil, nil, errors.New("Nil header") + return nil, nil, errors.New("nil header") } chdr, err := utils.UnmarshalChannelHeader(hdr.ChannelHeader) @@ -270,7 +281,7 @@ func validateConfigTransaction(data []byte, hdr *common.Header) error { // check for nil argument if data == nil || hdr == nil { - return errors.New("Nil arguments") + return errors.New("nil arguments") } // There is no need to do this validation here, the configtx.Validator handles this @@ -285,7 +296,7 @@ func validateEndorserTransaction(data []byte, hdr *common.Header) error { // check for nil argument if data == nil || hdr == nil { - return errors.New("Nil arguments") + return errors.New("nil arguments") } // if the type is ENDORSER_TRANSACTION we unmarshal a Transaction message @@ -296,7 +307,7 @@ func validateEndorserTransaction(data []byte, hdr *common.Header) error { // check for nil argument if tx == nil { - return errors.New("Nil transaction") + return errors.New("nil transaction") } // TODO: validate tx.Version @@ -305,7 +316,7 @@ func validateEndorserTransaction(data []byte, hdr *common.Header) error { // hlf version 1 only supports a single action per transaction if len(tx.Actions) != 1 { - return fmt.Errorf("Only one action per transaction is supported (tx contains %d)", len(tx.Actions)) + return errors.Errorf("only one action per transaction is supported, tx contains %d", len(tx.Actions)) } putilsLogger.Debugf("validateEndorserTransaction info: there are %d actions", len(tx.Actions)) @@ -313,7 +324,7 @@ func validateEndorserTransaction(data []byte, hdr *common.Header) error { for _, act := range tx.Actions { // check for nil argument if act == nil { - return errors.New("Nil action") + return errors.New("nil action") } // if the type is ENDORSER_TRANSACTION we unmarshal a SignatureHeader diff --git a/core/common/validation/msgvalidation_test.go b/core/common/validation/msgvalidation_test.go new file mode 100644 index 00000000000..0a0871c75a4 --- /dev/null +++ b/core/common/validation/msgvalidation_test.go @@ -0,0 +1,119 @@ +package validation + +import ( + "fmt" + "testing" + + "github.com/hyperledger/fabric/common/util" + "github.com/hyperledger/fabric/msp/mgmt" + "github.com/hyperledger/fabric/protos/common" + "github.com/hyperledger/fabric/protos/peer" + "github.com/hyperledger/fabric/protos/utils" + "github.com/stretchr/testify/assert" +) + +func createTestTransactionEnvelope(channel string, response *peer.Response, simRes []byte) (*common.Envelope, error) { + prop, sProp, err := createTestProposalAndSignedProposal(channel) + if err != nil { + return nil, fmt.Errorf("failed to create test proposal and signed proposal, err %s", err) + } + + // validate it + _, _, _, err = ValidateProposalMessage(sProp) + if err != nil { + return nil, fmt.Errorf("ValidateProposalMessage failed, err %s", err) + } + + // endorse it to get a proposal response + presp, err := utils.CreateProposalResponse(prop.Header, prop.Payload, response, simRes, nil, getChaincodeID(), nil, signer) + if err != nil { + return nil, fmt.Errorf("CreateProposalResponse failed, err %s", err) + } + + // assemble a transaction from that proposal and endorsement + tx, err := utils.CreateSignedTx(prop, signer, presp) + if err != nil { + return nil, fmt.Errorf("CreateSignedTx failed, err %s", err) + } + + return tx, nil +} + +func createTestProposalAndSignedProposal(channel string) (*peer.Proposal, *peer.SignedProposal, error) { + // get a toy proposal + prop, err := getProposal(channel) + if err != nil { + return nil, nil, fmt.Errorf("getProposal failed, err %s", err) + } + + // sign it + sProp, err := utils.GetSignedProposal(prop, signer) + if err != nil { + return nil, nil, fmt.Errorf("GetSignedProposal failed, err %s", err) + } + return prop, sProp, nil +} + +func setupMSPManagerNoMSPs(channel string) error { + err := mgmt.GetManagerForChain(channel).Setup(nil) + if err != nil { + return err + } + + return nil +} + +func TestCheckSignatureFromCreator(t *testing.T) { + response := &peer.Response{Status: 200} + simRes := []byte("simulation_result") + + env, err := createTestTransactionEnvelope(util.GetTestChainID(), response, simRes) + assert.Nil(t, err, "failed to create test transaction: %s", err) + assert.NotNil(t, env) + + // get the payload from the envelope + payload, err := utils.GetPayload(env) + assert.NoError(t, err, "GetPayload returns err %s", err) + + // validate the header + chdr, shdr, err := validateCommonHeader(payload.Header) + assert.NoError(t, err, "validateCommonHeader returns err %s", err) + + // validate the signature in the envelope + err = checkSignatureFromCreator(shdr.Creator, env.Signature, env.Payload, chdr.ChannelId) + assert.NoError(t, err, "checkSignatureFromCreator returns err %s", err) + + // corrupt the creator + err = checkSignatureFromCreator([]byte("junk"), env.Signature, env.Payload, chdr.ChannelId) + assert.Error(t, err) + assert.Contains(t, err.Error(), "MSP error: could not deserialize") + + // check nonexistent channel + err = checkSignatureFromCreator(shdr.Creator, env.Signature, env.Payload, "junkchannel") + assert.Error(t, err) + assert.Contains(t, err.Error(), "MSP error: channel doesn't exist") +} + +func TestValidateProposalMessage(t *testing.T) { + // nonexistent channel + fakeChannel := "fakechannel" + _, sProp, err := createTestProposalAndSignedProposal(fakeChannel) + assert.NoError(t, err) + // validate it - it should fail + _, _, _, err = ValidateProposalMessage(sProp) + assert.Error(t, err) + assert.Contains(t, err.Error(), fmt.Sprintf("access denied: channel [%s] creator org [%s]", fakeChannel, signerMSPId)) + + // invalid signature + _, sProp, err = createTestProposalAndSignedProposal(util.GetTestChainID()) + assert.NoError(t, err) + sigCopy := make([]byte, len(sProp.Signature)) + copy(sigCopy, sProp.Signature) + for i := 0; i < len(sProp.Signature); i++ { + sigCopy[i] = byte(int(sigCopy[i]+1) % 255) + } + // validate it - it should fail + _, _, _, err = ValidateProposalMessage(&peer.SignedProposal{ProposalBytes: sProp.ProposalBytes, Signature: sigCopy}) + assert.Error(t, err) + assert.Contains(t, err.Error(), fmt.Sprintf("access denied: channel [%s] creator org [%s]", util.GetTestChainID(), signerMSPId)) +} diff --git a/msp/mgmt/mgmt.go b/msp/mgmt/mgmt.go index 1de2816974a..cd50144a131 100644 --- a/msp/mgmt/mgmt.go +++ b/msp/mgmt/mgmt.go @@ -77,6 +77,30 @@ var localMsp msp.MSP var mspMap map[string]msp.MSPManager = make(map[string]msp.MSPManager) var mspLogger = flogging.MustGetLogger("msp") +// TODO - this is a temporary solution to allow the peer to track whether the +// MSPManager has been setup for a channel, which indicates whether the channel +// exists or not +type mspMgmtMgr struct { + msp.MSPManager + // track whether this MSPManager has been setup successfully + up bool +} + +func (mgr *mspMgmtMgr) DeserializeIdentity(serializedIdentity []byte) (msp.Identity, error) { + if !mgr.up { + return nil, errors.New("channel doesn't exist") + } + return mgr.MSPManager.DeserializeIdentity(serializedIdentity) +} + +func (mgr *mspMgmtMgr) Setup(msps []msp.MSP) error { + err := mgr.MSPManager.Setup(msps) + if err == nil { + mgr.up = true + } + return err +} + // GetManagerForChain returns the msp manager for the supplied // chain; if no such manager exists, one is created func GetManagerForChain(chainID string) msp.MSPManager { @@ -85,15 +109,16 @@ func GetManagerForChain(chainID string) msp.MSPManager { mspMgr, ok := mspMap[chainID] if !ok { - mspLogger.Debugf("Created new msp manager for chain %s", chainID) - mspMgr = msp.NewMSPManager() - mspMap[chainID] = mspMgr + mspLogger.Debugf("Created new msp manager for channel `%s`", chainID) + mspMgmtMgr := &mspMgmtMgr{msp.NewMSPManager(), false} + mspMap[chainID] = mspMgmtMgr + mspMgr = mspMgmtMgr } else { - // check for internal mspManagerImpl type. if a different type is found, - // it's because a developer has added a new type that implements the - // MSPManager interface and should add a case to the logic above to handle - // it. - if reflect.TypeOf(mspMgr).Elem().Name() != "mspManagerImpl" { + // check for internal mspManagerImpl and mspMgmtMgr types. if a different + // type is found, it's because a developer has added a new type that + // implements the MSPManager interface and should add a case to the logic + // above to handle it. + if !(reflect.TypeOf(mspMgr).Elem().Name() == "mspManagerImpl" || reflect.TypeOf(mspMgr).Elem().Name() == "mspMgmtMgr") { panic("Found unexpected MSPManager type.") } mspLogger.Debugf("Returning existing manager for channel '%s'", chainID) @@ -122,7 +147,7 @@ func XXXSetMSPManager(chainID string, manager msp.MSPManager) { m.Lock() defer m.Unlock() - mspMap[chainID] = manager + mspMap[chainID] = &mspMgmtMgr{manager, true} } // GetLocalMSP returns the local msp (and creates it if it doesn't exist) diff --git a/msp/mgmt/mgmt_test.go b/msp/mgmt/mgmt_test.go index 60a3f059a03..e92840f594e 100644 --- a/msp/mgmt/mgmt_test.go +++ b/msp/mgmt/mgmt_test.go @@ -19,6 +19,8 @@ package mgmt import ( "testing" + "github.com/hyperledger/fabric/common/util" + "github.com/hyperledger/fabric/core/config" "github.com/hyperledger/fabric/msp" "github.com/stretchr/testify/assert" ) @@ -28,14 +30,14 @@ func TestGetManagerForChains(t *testing.T) { mspMgr1 := GetManagerForChain("test") // ensure MSPManager is set if mspMgr1 == nil { - t.FailNow() + t.Fatal("mspMgr1 fail") } // MSPManager for channel now exists mspMgr2 := GetManagerForChain("test") // ensure MSPManager returned matches the first result if mspMgr2 != mspMgr1 { - t.FailNow() + t.Fatal("mspMgr2 != mspMgr1 fail") } } @@ -81,3 +83,64 @@ func TestUpdateLocalMspCache(t *testing.T) { t.Fatalf("firstMsp != secondMsp") } } + +func TestNewMSPMgmtMgr(t *testing.T) { + err := LoadMSPSetupForTesting() + assert.Nil(t, err) + + // test for nonexistent channel + mspMgmtMgr := GetManagerForChain("fake") + + id := GetLocalSigningIdentityOrPanic() + assert.NotNil(t, id) + + serializedID, err := id.Serialize() + if err != nil { + t.Fatalf("Serialize should have succeeded, got err %s", err) + return + } + + idBack, err := mspMgmtMgr.DeserializeIdentity(serializedID) + assert.Error(t, err) + assert.Contains(t, err.Error(), "channel doesn't exist") + assert.Nil(t, idBack, "deserialized identity should have been nil") + + // test for existing channel + mspMgmtMgr = GetManagerForChain(util.GetTestChainID()) + + id = GetLocalSigningIdentityOrPanic() + assert.NotNil(t, id) + + serializedID, err = id.Serialize() + if err != nil { + t.Fatalf("Serialize should have succeeded, got err %s", err) + return + } + + idBack, err = mspMgmtMgr.DeserializeIdentity(serializedID) + assert.NoError(t, err) + assert.NotNil(t, idBack, "deserialized identity should not have been nil") +} + +func LoadMSPSetupForTesting() error { + dir, err := config.GetDevMspDir() + if err != nil { + return err + } + conf, err := msp.GetLocalMspConfig(dir, nil, "DEFAULT") + if err != nil { + return err + } + + err = GetLocalMSP().Setup(conf) + if err != nil { + return err + } + + err = GetManagerForChain(util.GetTestChainID()).Setup([]msp.MSP{GetLocalMSP()}) + if err != nil { + return err + } + + return nil +} diff --git a/msp/msp_test.go b/msp/msp_test.go index 1cfd8eb4621..1d76b0ef9bc 100644 --- a/msp/msp_test.go +++ b/msp/msp_test.go @@ -400,6 +400,11 @@ func TestSerializeIdentitiesWithMSPManager(t *testing.T) { _, err = mspMgr.DeserializeIdentity(serializedID) assert.Error(t, err) + assert.Contains(t, err.Error(), fmt.Sprintf("MSP %s is unknown", sid.Mspid)) + + _, err = mspMgr.DeserializeIdentity([]byte("barf")) + assert.Error(t, err) + assert.Contains(t, err.Error(), "could not deserialize") } func TestIdentitiesGetters(t *testing.T) {