Skip to content

Commit 720a258

Browse files
committed
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 <[email protected]>
1 parent e829d2e commit 720a258

File tree

2 files changed

+135
-28
lines changed

2 files changed

+135
-28
lines changed

core/endorser/endorser_test.go

+85-13
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,18 @@ func closeListenerAndSleep(l net.Listener) {
116116
}
117117
}
118118

119-
//getProposal gets the proposal for the chaincode invocation
120-
//Currently supported only for Invokes (Queries still go through devops client)
121-
func getInvokeProposal(cis *pb.ChaincodeInvocationSpec, chainID string, creator []byte) (*pb.Proposal, error) {
122-
proposal, _, err := pbutils.CreateChaincodeProposal(common.HeaderType_ENDORSER_TRANSACTION, chainID, cis, creator)
119+
// getInvokeProposal gets the proposal for the chaincode invocation
120+
// Currently supported only for Invokes
121+
// It returns the proposal and the transaction id associated to the proposal
122+
func getInvokeProposal(cis *pb.ChaincodeInvocationSpec, chainID string, creator []byte) (*pb.Proposal, string, error) {
123+
return pbutils.CreateChaincodeProposal(common.HeaderType_ENDORSER_TRANSACTION, chainID, cis, creator)
124+
}
123125

124-
return proposal, err
126+
// getInvokeProposalOverride allows to get a proposal for the chaincode invocation
127+
// overriding transaction id and nonce which are by default auto-generated.
128+
// It returns the proposal and the transaction id associated to the proposal
129+
func getInvokeProposalOverride(txid string, cis *pb.ChaincodeInvocationSpec, chainID string, nonce, creator []byte) (*pb.Proposal, string, error) {
130+
return pbutils.CreateChaincodeProposalWithTxIDNonceAndTransient(txid, common.HeaderType_ENDORSER_TRANSACTION, chainID, cis, nonce, creator, nil)
125131
}
126132

127133
func getDeployProposal(cds *pb.ChaincodeDeploymentSpec, chainID string, creator []byte) (*pb.Proposal, error) {
@@ -152,7 +158,7 @@ func getDeployOrUpgradeProposal(cds *pb.ChaincodeDeploymentSpec, chainID string,
152158

153159
//...and get the proposal for it
154160
var prop *pb.Proposal
155-
if prop, err = getInvokeProposal(lcccSpec, chainID, creator); err != nil {
161+
if prop, _, err = getInvokeProposal(lcccSpec, chainID, creator); err != nil {
156162
return nil, err
157163
}
158164

@@ -235,7 +241,40 @@ func deployOrUpgrade(endorserServer pb.EndorserServer, chainID string, spec *pb.
235241
return resp, prop, err
236242
}
237243

238-
func invoke(chainID string, spec *pb.ChaincodeSpec) (*pb.ProposalResponse, error) {
244+
func invoke(chainID string, spec *pb.ChaincodeSpec) (*pb.Proposal, *pb.ProposalResponse, string, []byte, error) {
245+
invocation := &pb.ChaincodeInvocationSpec{ChaincodeSpec: spec}
246+
247+
creator, err := signer.Serialize()
248+
if err != nil {
249+
return nil, nil, "", nil, err
250+
}
251+
252+
var prop *pb.Proposal
253+
prop, txID, err := getInvokeProposal(invocation, chainID, creator)
254+
if err != nil {
255+
return nil, nil, "", nil, fmt.Errorf("Error creating proposal %s: %s\n", spec.ChaincodeId, err)
256+
}
257+
258+
nonce, err := pbutils.GetNonce(prop)
259+
if err != nil {
260+
return nil, nil, "", nil, fmt.Errorf("Failed getting nonce %s: %s\n", spec.ChaincodeId, err)
261+
}
262+
263+
var signedProp *pb.SignedProposal
264+
signedProp, err = getSignedProposal(prop, signer)
265+
if err != nil {
266+
return nil, nil, "", nil, err
267+
}
268+
269+
resp, err := endorserServer.ProcessProposal(context.Background(), signedProp)
270+
if err != nil {
271+
return nil, nil, "", nil, fmt.Errorf("Error endorsing %s: %s\n", spec.ChaincodeId, err)
272+
}
273+
274+
return prop, resp, txID, nonce, err
275+
}
276+
277+
func invokeWithOverride(txid string, chainID string, spec *pb.ChaincodeSpec, nonce []byte) (*pb.ProposalResponse, error) {
239278
invocation := &pb.ChaincodeInvocationSpec{ChaincodeSpec: spec}
240279

241280
creator, err := signer.Serialize()
@@ -244,9 +283,9 @@ func invoke(chainID string, spec *pb.ChaincodeSpec) (*pb.ProposalResponse, error
244283
}
245284

246285
var prop *pb.Proposal
247-
prop, err = getInvokeProposal(invocation, chainID, creator)
286+
prop, _, err = getInvokeProposalOverride(txid, invocation, chainID, nonce, creator)
248287
if err != nil {
249-
return nil, fmt.Errorf("Error creating proposal %s: %s\n", spec.ChaincodeId, err)
288+
return nil, fmt.Errorf("Error creating proposal with override %s %s: %s\n", txid, spec.ChaincodeId, err)
250289
}
251290

252291
var signedProp *pb.SignedProposal
@@ -257,7 +296,7 @@ func invoke(chainID string, spec *pb.ChaincodeSpec) (*pb.ProposalResponse, error
257296

258297
resp, err := endorserServer.ProcessProposal(context.Background(), signedProp)
259298
if err != nil {
260-
return nil, fmt.Errorf("Error endorsing %s: %s\n", spec.ChaincodeId, err)
299+
return nil, fmt.Errorf("Error endorsing %s %s: %s\n", txid, spec.ChaincodeId, err)
261300
}
262301

263302
return resp, err
@@ -352,21 +391,54 @@ func TestDeployAndInvoke(t *testing.T) {
352391
err = endorserServer.(*Endorser).commitTxSimulation(prop, chainID, signer, resp, nextBlockNumber)
353392
if err != nil {
354393
t.Fail()
355-
t.Logf("Error committing <%s>: %s", chaincodeID1, err)
394+
t.Logf("Error committing deploy <%s>: %s", chaincodeID1, err)
356395
chaincode.GetChain().Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: &pb.ChaincodeSpec{ChaincodeId: chaincodeID}})
357396
return
358397
}
359398

360399
f = "invoke"
361400
invokeArgs := append([]string{f}, args...)
362401
spec = &pb.ChaincodeSpec{Type: 1, ChaincodeId: chaincodeID, Input: &pb.ChaincodeInput{Args: util.ToChaincodeArgs(invokeArgs...)}}
363-
_, err = invoke(chainID, spec)
402+
prop, resp, txid, nonce, err := invoke(chainID, spec)
364403
if err != nil {
365404
t.Fail()
366405
t.Logf("Error invoking transaction: %s", err)
367406
chaincode.GetChain().Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: &pb.ChaincodeSpec{ChaincodeId: chaincodeID}})
368407
return
369408
}
409+
// Commit invoke
410+
nextBlockNumber++
411+
err = endorserServer.(*Endorser).commitTxSimulation(prop, chainID, signer, resp, nextBlockNumber)
412+
if err != nil {
413+
t.Fail()
414+
t.Logf("Error committing first invoke <%s>: %s", chaincodeID1, err)
415+
chaincode.GetChain().Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: &pb.ChaincodeSpec{ChaincodeId: chaincodeID}})
416+
return
417+
}
418+
419+
// Now test for an invalid TxID
420+
f = "invoke"
421+
invokeArgs = append([]string{f}, args...)
422+
spec = &pb.ChaincodeSpec{Type: 1, ChaincodeId: chaincodeID, Input: &pb.ChaincodeInput{Args: util.ToChaincodeArgs(invokeArgs...)}}
423+
_, err = invokeWithOverride("invalid_tx_id", chainID, spec, nonce)
424+
if err == nil {
425+
t.Fail()
426+
t.Log("Replay attack protection faild. Transaction with invalid txid passed")
427+
chaincode.GetChain().Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: &pb.ChaincodeSpec{ChaincodeId: chaincodeID}})
428+
return
429+
}
430+
431+
// Now test for duplicated TxID
432+
f = "invoke"
433+
invokeArgs = append([]string{f}, args...)
434+
spec = &pb.ChaincodeSpec{Type: 1, ChaincodeId: chaincodeID, Input: &pb.ChaincodeInput{Args: util.ToChaincodeArgs(invokeArgs...)}}
435+
_, err = invokeWithOverride(txid, chainID, spec, nonce)
436+
if err == nil {
437+
t.Fail()
438+
t.Log("Replay attack protection faild. Transaction with duplicaged txid passed")
439+
chaincode.GetChain().Stop(ctxt, cccid, &pb.ChaincodeDeploymentSpec{ChaincodeSpec: &pb.ChaincodeSpec{ChaincodeId: chaincodeID}})
440+
return
441+
}
370442

371443
fmt.Printf("Invoke test passed\n")
372444
t.Logf("Invoke test passed")
@@ -405,7 +477,7 @@ func TestDeployAndUpgrade(t *testing.T) {
405477
return
406478
}
407479

408-
var nextBlockNumber uint64 = 1 // something above created block 0
480+
var nextBlockNumber uint64 = 2 // something above created block 0
409481
err = endorserServer.(*Endorser).commitTxSimulation(prop, chainID, signer, resp, nextBlockNumber)
410482
if err != nil {
411483
t.Fail()

protos/utils/proputils.go

+50-15
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,31 @@ func GetHeader(bytes []byte) (*common.Header, error) {
103103
return hdr, nil
104104
}
105105

106+
// GetNonce returns the nonce used in Proposal
107+
func GetNonce(prop *peer.Proposal) ([]byte, error) {
108+
// get back the header
109+
hdr, err := GetHeader(prop.Header)
110+
if err != nil {
111+
return nil, fmt.Errorf("Could not extract the header from the proposal: %s", err)
112+
}
113+
if common.HeaderType(hdr.ChannelHeader.Type) != common.HeaderType_ENDORSER_TRANSACTION &&
114+
common.HeaderType(hdr.ChannelHeader.Type) != common.HeaderType_CONFIG {
115+
return nil, fmt.Errorf("Invalid proposal type expected ENDORSER_TRANSACTION or CONFIG. Was: %d", hdr.ChannelHeader.Type)
116+
}
117+
118+
if hdr.SignatureHeader == nil {
119+
return nil, errors.New("Invalid signature header. It must be different from nil.")
120+
}
121+
122+
ccPropPayload := &peer.ChaincodeProposalPayload{}
123+
err = proto.Unmarshal(prop.Payload, ccPropPayload)
124+
if err != nil {
125+
return nil, err
126+
}
127+
128+
return hdr.SignatureHeader.Nonce, nil
129+
}
130+
106131
// GetChaincodeHeaderExtension get chaincode header extension given header
107132
func GetChaincodeHeaderExtension(hdr *common.Header) (*peer.ChaincodeHeaderExtension, error) {
108133
chaincodeHdrExt := &peer.ChaincodeHeaderExtension{}
@@ -268,47 +293,53 @@ func GetSignaturePolicyEnvelope(bytes []byte) (*common.SignaturePolicyEnvelope,
268293
return p, nil
269294
}
270295

271-
// CreateChaincodeProposal creates a proposal from given input
296+
// CreateChaincodeProposal creates a proposal from given input.
297+
// It returns the proposal and the transaction id associated to the proposal
272298
func CreateChaincodeProposal(typ common.HeaderType, chainID string, cis *peer.ChaincodeInvocationSpec, creator []byte) (*peer.Proposal, string, error) {
273299
return CreateChaincodeProposalWithTransient(typ, chainID, cis, creator, nil)
274300
}
275301

276302
// CreateChaincodeProposalWithTransient creates a proposal from given input
303+
// It returns the proposal and the transaction id associated to the proposal
277304
func CreateChaincodeProposalWithTransient(typ common.HeaderType, chainID string, cis *peer.ChaincodeInvocationSpec, creator []byte, transientMap map[string][]byte) (*peer.Proposal, string, error) {
278-
ccHdrExt := &peer.ChaincodeHeaderExtension{ChaincodeId: cis.ChaincodeSpec.ChaincodeId}
279-
ccHdrExtBytes, err := proto.Marshal(ccHdrExt)
305+
// generate a random nonce
306+
nonce, err := primitives.GetRandomNonce()
280307
if err != nil {
281308
return nil, "", err
282309
}
283310

284-
cisBytes, err := proto.Marshal(cis)
311+
// compute txid
312+
txid, err := ComputeProposalTxID(nonce, creator)
285313
if err != nil {
286314
return nil, "", err
287315
}
288316

289-
ccPropPayload := &peer.ChaincodeProposalPayload{Input: cisBytes, TransientMap: transientMap}
290-
ccPropPayloadBytes, err := proto.Marshal(ccPropPayload)
317+
return CreateChaincodeProposalWithTxIDNonceAndTransient(txid, typ, chainID, cis, nonce, creator, transientMap)
318+
}
319+
320+
// CreateChaincodeProposalWithTxIDNonceAndTransient creates a proposal from given input
321+
func CreateChaincodeProposalWithTxIDNonceAndTransient(txid string, typ common.HeaderType, chainID string, cis *peer.ChaincodeInvocationSpec, nonce, creator []byte, transientMap map[string][]byte) (*peer.Proposal, string, error) {
322+
ccHdrExt := &peer.ChaincodeHeaderExtension{ChaincodeId: cis.ChaincodeSpec.ChaincodeId}
323+
ccHdrExtBytes, err := proto.Marshal(ccHdrExt)
291324
if err != nil {
292325
return nil, "", err
293326
}
294327

295-
// generate a random nonce
296-
nonce, err := primitives.GetRandomNonce()
328+
cisBytes, err := proto.Marshal(cis)
297329
if err != nil {
298330
return nil, "", err
299331
}
300332

301-
// epoch
302-
// TODO: it is now set to zero. This must be changed once we
303-
// get a more appropriate mechanism to handle it in.
304-
var epoch uint64 = 0
305-
306-
// compute txid
307-
txid, err := ComputeProposalTxID(nonce, creator)
333+
ccPropPayload := &peer.ChaincodeProposalPayload{Input: cisBytes, TransientMap: transientMap}
334+
ccPropPayloadBytes, err := proto.Marshal(ccPropPayload)
308335
if err != nil {
309336
return nil, "", err
310337
}
311338

339+
// TODO: epoch is now set to zero. This must be changed once we
340+
// get a more appropriate mechanism to handle it in.
341+
var epoch uint64 = 0
342+
312343
hdr := &common.Header{ChannelHeader: &common.ChannelHeader{
313344
Type: int32(typ),
314345
TxId: txid,
@@ -525,6 +556,8 @@ func createProposalFromCDS(chainID string, cds *peer.ChaincodeDeploymentSpec, cr
525556
return CreateProposalFromCIS(common.HeaderType_ENDORSER_TRANSACTION, chainID, lcccSpec, creator)
526557
}
527558

559+
// ComputeProposalTxID computes TxID as the Hash computed
560+
// over the concatenation of nonce and creator.
528561
func ComputeProposalTxID(nonce, creator []byte) (string, error) {
529562
// TODO: Get the Hash function to be used from
530563
// channel configuration
@@ -537,6 +570,8 @@ func ComputeProposalTxID(nonce, creator []byte) (string, error) {
537570
return hex.EncodeToString(digest), nil
538571
}
539572

573+
// CheckProposalTxID checks that txid is equal to the Hash computed
574+
// over the concatenation of nonce and creator.
540575
func CheckProposalTxID(txid string, nonce, creator []byte) error {
541576
computedTxID, err := ComputeProposalTxID(nonce, creator)
542577
if err != nil {

0 commit comments

Comments
 (0)