Skip to content

Commit

Permalink
Integration Test for Replay Attack Protection
Browse files Browse the repository at this point in the history
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 <adc@zurich.ibm.com>
  • Loading branch information
adecaro committed Feb 20, 2017
1 parent e829d2e commit 720a258
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 28 deletions.
98 changes: 85 additions & 13 deletions core/endorser/endorser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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()
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -352,21 +391,54 @@ 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
}

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")
Expand Down Expand Up @@ -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()
Expand Down
65 changes: 50 additions & 15 deletions protos/utils/proputils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand Down

0 comments on commit 720a258

Please sign in to comment.