From 720a258b6df66a7cbe7cb2604eca30fddbe622b6 Mon Sep 17 00:00:00 2001 From: Angelo De Caro Date: Sun, 19 Feb 2017 10:26:02 +0100 Subject: [PATCH] Integration Test for Replay Attack Protection This change-set introduces a new test to verify that 1. proposals with invalid txid get rejected 2. proposals with a txid that already exists on the ledger get rejected. In addition, the change-set add an additional unit-test on txid computation. Change-Id: Ic821378f42e6d1e6d3cc3b36db843d118c3a8a7c Signed-off-by: Angelo De Caro --- core/endorser/endorser_test.go | 98 +++++++++++++++++++++++++++++----- protos/utils/proputils.go | 65 ++++++++++++++++------ 2 files changed, 135 insertions(+), 28 deletions(-) diff --git a/core/endorser/endorser_test.go b/core/endorser/endorser_test.go index 823255d1b3b..587f1c65aa2 100644 --- a/core/endorser/endorser_test.go +++ b/core/endorser/endorser_test.go @@ -116,12 +116,18 @@ func closeListenerAndSleep(l net.Listener) { } } -//getProposal gets the proposal for the chaincode invocation -//Currently supported only for Invokes (Queries still go through devops client) -func getInvokeProposal(cis *pb.ChaincodeInvocationSpec, chainID string, creator []byte) (*pb.Proposal, error) { - proposal, _, err := pbutils.CreateChaincodeProposal(common.HeaderType_ENDORSER_TRANSACTION, chainID, cis, creator) +// getInvokeProposal gets the proposal for the chaincode invocation +// Currently supported only for Invokes +// It returns the proposal and the transaction id associated to the proposal +func getInvokeProposal(cis *pb.ChaincodeInvocationSpec, chainID string, creator []byte) (*pb.Proposal, string, error) { + return pbutils.CreateChaincodeProposal(common.HeaderType_ENDORSER_TRANSACTION, chainID, cis, creator) +} - return proposal, err +// getInvokeProposalOverride allows to get a proposal for the chaincode invocation +// overriding transaction id and nonce which are by default auto-generated. +// It returns the proposal and the transaction id associated to the proposal +func getInvokeProposalOverride(txid string, cis *pb.ChaincodeInvocationSpec, chainID string, nonce, creator []byte) (*pb.Proposal, string, error) { + return pbutils.CreateChaincodeProposalWithTxIDNonceAndTransient(txid, common.HeaderType_ENDORSER_TRANSACTION, chainID, cis, nonce, creator, nil) } func getDeployProposal(cds *pb.ChaincodeDeploymentSpec, chainID string, creator []byte) (*pb.Proposal, error) { @@ -152,7 +158,7 @@ func getDeployOrUpgradeProposal(cds *pb.ChaincodeDeploymentSpec, chainID string, //...and get the proposal for it var prop *pb.Proposal - if prop, err = getInvokeProposal(lcccSpec, chainID, creator); err != nil { + if prop, _, err = getInvokeProposal(lcccSpec, chainID, creator); err != nil { return nil, err } @@ -235,7 +241,40 @@ func deployOrUpgrade(endorserServer pb.EndorserServer, chainID string, spec *pb. return resp, prop, err } -func invoke(chainID string, spec *pb.ChaincodeSpec) (*pb.ProposalResponse, error) { +func invoke(chainID string, spec *pb.ChaincodeSpec) (*pb.Proposal, *pb.ProposalResponse, string, []byte, error) { + invocation := &pb.ChaincodeInvocationSpec{ChaincodeSpec: spec} + + creator, err := signer.Serialize() + if err != nil { + return nil, nil, "", nil, err + } + + var prop *pb.Proposal + prop, txID, err := getInvokeProposal(invocation, chainID, creator) + if err != nil { + return nil, nil, "", nil, fmt.Errorf("Error creating proposal %s: %s\n", spec.ChaincodeId, err) + } + + nonce, err := pbutils.GetNonce(prop) + if err != nil { + return nil, nil, "", nil, fmt.Errorf("Failed getting nonce %s: %s\n", spec.ChaincodeId, err) + } + + var signedProp *pb.SignedProposal + signedProp, err = getSignedProposal(prop, signer) + if err != nil { + return nil, nil, "", nil, err + } + + resp, err := endorserServer.ProcessProposal(context.Background(), signedProp) + if err != nil { + return nil, nil, "", nil, fmt.Errorf("Error endorsing %s: %s\n", spec.ChaincodeId, err) + } + + return prop, resp, txID, nonce, err +} + +func invokeWithOverride(txid string, chainID string, spec *pb.ChaincodeSpec, nonce []byte) (*pb.ProposalResponse, error) { invocation := &pb.ChaincodeInvocationSpec{ChaincodeSpec: spec} creator, err := signer.Serialize() @@ -244,9 +283,9 @@ func invoke(chainID string, spec *pb.ChaincodeSpec) (*pb.ProposalResponse, error } var prop *pb.Proposal - prop, err = getInvokeProposal(invocation, chainID, creator) + prop, _, err = getInvokeProposalOverride(txid, invocation, chainID, nonce, creator) if err != nil { - return nil, fmt.Errorf("Error creating proposal %s: %s\n", spec.ChaincodeId, err) + return nil, fmt.Errorf("Error creating proposal with override %s %s: %s\n", txid, spec.ChaincodeId, err) } var signedProp *pb.SignedProposal @@ -257,7 +296,7 @@ func invoke(chainID string, spec *pb.ChaincodeSpec) (*pb.ProposalResponse, error resp, err := endorserServer.ProcessProposal(context.Background(), signedProp) if err != nil { - return nil, fmt.Errorf("Error endorsing %s: %s\n", spec.ChaincodeId, err) + return nil, fmt.Errorf("Error endorsing %s %s: %s\n", txid, spec.ChaincodeId, err) } return resp, err @@ -352,7 +391,7 @@ func TestDeployAndInvoke(t *testing.T) { err = endorserServer.(*Endorser).commitTxSimulation(prop, chainID, signer, resp, nextBlockNumber) if err != nil { t.Fail() - t.Logf("Error committing <%s>: %s", chaincodeID1, err) + t.Logf("Error committing deploy <%s>: %s", chaincodeID1, err) chaincode.GetChain().Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: &pb.ChaincodeSpec{ChaincodeId: chaincodeID}}) return } @@ -360,13 +399,46 @@ func TestDeployAndInvoke(t *testing.T) { f = "invoke" invokeArgs := append([]string{f}, args...) spec = &pb.ChaincodeSpec{Type: 1, ChaincodeId: chaincodeID, Input: &pb.ChaincodeInput{Args: util.ToChaincodeArgs(invokeArgs...)}} - _, err = invoke(chainID, spec) + prop, resp, txid, nonce, err := invoke(chainID, spec) if err != nil { t.Fail() t.Logf("Error invoking transaction: %s", err) chaincode.GetChain().Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: &pb.ChaincodeSpec{ChaincodeId: chaincodeID}}) return } + // Commit invoke + nextBlockNumber++ + err = endorserServer.(*Endorser).commitTxSimulation(prop, chainID, signer, resp, nextBlockNumber) + if err != nil { + t.Fail() + t.Logf("Error committing first invoke <%s>: %s", chaincodeID1, err) + chaincode.GetChain().Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: &pb.ChaincodeSpec{ChaincodeId: chaincodeID}}) + return + } + + // Now test for an invalid TxID + f = "invoke" + invokeArgs = append([]string{f}, args...) + spec = &pb.ChaincodeSpec{Type: 1, ChaincodeId: chaincodeID, Input: &pb.ChaincodeInput{Args: util.ToChaincodeArgs(invokeArgs...)}} + _, err = invokeWithOverride("invalid_tx_id", chainID, spec, nonce) + if err == nil { + t.Fail() + t.Log("Replay attack protection faild. Transaction with invalid txid passed") + chaincode.GetChain().Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: &pb.ChaincodeSpec{ChaincodeId: chaincodeID}}) + return + } + + // Now test for duplicated TxID + f = "invoke" + invokeArgs = append([]string{f}, args...) + spec = &pb.ChaincodeSpec{Type: 1, ChaincodeId: chaincodeID, Input: &pb.ChaincodeInput{Args: util.ToChaincodeArgs(invokeArgs...)}} + _, err = invokeWithOverride(txid, chainID, spec, nonce) + if err == nil { + t.Fail() + t.Log("Replay attack protection faild. Transaction with duplicaged txid passed") + chaincode.GetChain().Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: &pb.ChaincodeSpec{ChaincodeId: chaincodeID}}) + return + } fmt.Printf("Invoke test passed\n") t.Logf("Invoke test passed") @@ -405,7 +477,7 @@ func TestDeployAndUpgrade(t *testing.T) { return } - var nextBlockNumber uint64 = 1 // something above created block 0 + var nextBlockNumber uint64 = 2 // something above created block 0 err = endorserServer.(*Endorser).commitTxSimulation(prop, chainID, signer, resp, nextBlockNumber) if err != nil { t.Fail() diff --git a/protos/utils/proputils.go b/protos/utils/proputils.go index dc326fd0b0f..f6c52a9f073 100644 --- a/protos/utils/proputils.go +++ b/protos/utils/proputils.go @@ -103,6 +103,31 @@ func GetHeader(bytes []byte) (*common.Header, error) { return hdr, nil } +// GetNonce returns the nonce used in Proposal +func GetNonce(prop *peer.Proposal) ([]byte, error) { + // get back the header + hdr, err := GetHeader(prop.Header) + if err != nil { + return nil, fmt.Errorf("Could not extract the header from the proposal: %s", err) + } + if common.HeaderType(hdr.ChannelHeader.Type) != common.HeaderType_ENDORSER_TRANSACTION && + common.HeaderType(hdr.ChannelHeader.Type) != common.HeaderType_CONFIG { + return nil, fmt.Errorf("Invalid proposal type expected ENDORSER_TRANSACTION or CONFIG. Was: %d", hdr.ChannelHeader.Type) + } + + if hdr.SignatureHeader == nil { + return nil, errors.New("Invalid signature header. It must be different from nil.") + } + + ccPropPayload := &peer.ChaincodeProposalPayload{} + err = proto.Unmarshal(prop.Payload, ccPropPayload) + if err != nil { + return nil, err + } + + return hdr.SignatureHeader.Nonce, nil +} + // GetChaincodeHeaderExtension get chaincode header extension given header func GetChaincodeHeaderExtension(hdr *common.Header) (*peer.ChaincodeHeaderExtension, error) { chaincodeHdrExt := &peer.ChaincodeHeaderExtension{} @@ -268,47 +293,53 @@ func GetSignaturePolicyEnvelope(bytes []byte) (*common.SignaturePolicyEnvelope, return p, nil } -// CreateChaincodeProposal creates a proposal from given input +// CreateChaincodeProposal creates a proposal from given input. +// It returns the proposal and the transaction id associated to the proposal func CreateChaincodeProposal(typ common.HeaderType, chainID string, cis *peer.ChaincodeInvocationSpec, creator []byte) (*peer.Proposal, string, error) { return CreateChaincodeProposalWithTransient(typ, chainID, cis, creator, nil) } // CreateChaincodeProposalWithTransient creates a proposal from given input +// It returns the proposal and the transaction id associated to the proposal func CreateChaincodeProposalWithTransient(typ common.HeaderType, chainID string, cis *peer.ChaincodeInvocationSpec, creator []byte, transientMap map[string][]byte) (*peer.Proposal, string, error) { - ccHdrExt := &peer.ChaincodeHeaderExtension{ChaincodeId: cis.ChaincodeSpec.ChaincodeId} - ccHdrExtBytes, err := proto.Marshal(ccHdrExt) + // generate a random nonce + nonce, err := primitives.GetRandomNonce() if err != nil { return nil, "", err } - cisBytes, err := proto.Marshal(cis) + // compute txid + txid, err := ComputeProposalTxID(nonce, creator) if err != nil { return nil, "", err } - ccPropPayload := &peer.ChaincodeProposalPayload{Input: cisBytes, TransientMap: transientMap} - ccPropPayloadBytes, err := proto.Marshal(ccPropPayload) + return CreateChaincodeProposalWithTxIDNonceAndTransient(txid, typ, chainID, cis, nonce, creator, transientMap) +} + +// CreateChaincodeProposalWithTxIDNonceAndTransient creates a proposal from given input +func CreateChaincodeProposalWithTxIDNonceAndTransient(txid string, typ common.HeaderType, chainID string, cis *peer.ChaincodeInvocationSpec, nonce, creator []byte, transientMap map[string][]byte) (*peer.Proposal, string, error) { + ccHdrExt := &peer.ChaincodeHeaderExtension{ChaincodeId: cis.ChaincodeSpec.ChaincodeId} + ccHdrExtBytes, err := proto.Marshal(ccHdrExt) if err != nil { return nil, "", err } - // generate a random nonce - nonce, err := primitives.GetRandomNonce() + cisBytes, err := proto.Marshal(cis) if err != nil { return nil, "", err } - // epoch - // TODO: it is now set to zero. This must be changed once we - // get a more appropriate mechanism to handle it in. - var epoch uint64 = 0 - - // compute txid - txid, err := ComputeProposalTxID(nonce, creator) + ccPropPayload := &peer.ChaincodeProposalPayload{Input: cisBytes, TransientMap: transientMap} + ccPropPayloadBytes, err := proto.Marshal(ccPropPayload) if err != nil { return nil, "", err } + // TODO: epoch is now set to zero. This must be changed once we + // get a more appropriate mechanism to handle it in. + var epoch uint64 = 0 + hdr := &common.Header{ChannelHeader: &common.ChannelHeader{ Type: int32(typ), TxId: txid, @@ -525,6 +556,8 @@ func createProposalFromCDS(chainID string, cds *peer.ChaincodeDeploymentSpec, cr return CreateProposalFromCIS(common.HeaderType_ENDORSER_TRANSACTION, chainID, lcccSpec, creator) } +// ComputeProposalTxID computes TxID as the Hash computed +// over the concatenation of nonce and creator. func ComputeProposalTxID(nonce, creator []byte) (string, error) { // TODO: Get the Hash function to be used from // channel configuration @@ -537,6 +570,8 @@ func ComputeProposalTxID(nonce, creator []byte) (string, error) { return hex.EncodeToString(digest), nil } +// CheckProposalTxID checks that txid is equal to the Hash computed +// over the concatenation of nonce and creator. func CheckProposalTxID(txid string, nonce, creator []byte) error { computedTxID, err := ComputeProposalTxID(nonce, creator) if err != nil {