Skip to content
This repository was archived by the owner on Apr 25, 2025. It is now read-only.

Commit b54994a

Browse files
committed
[FAB-8938] Ledger Client: Signature validation
Change-Id: I82f29e19df11f16712de7c3b649fdc06fbb6da9a Signed-off-by: Sandra Vrtikapa <sandra.vrtikapa@securekey.com>
1 parent cc16a4a commit b54994a

File tree

5 files changed

+137
-48
lines changed

5 files changed

+137
-48
lines changed

pkg/client/ledger/ledger.go

Lines changed: 66 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@ import (
1313
"time"
1414

1515
"github.com/golang/protobuf/proto"
16+
1617
"github.com/hyperledger/fabric-sdk-go/pkg/common/providers/context"
1718
"github.com/hyperledger/fabric-sdk-go/pkg/common/providers/fab"
19+
"github.com/hyperledger/fabric-sdk-go/pkg/util/errors/status"
20+
1821
"github.com/hyperledger/fabric-sdk-go/pkg/fab/chconfig"
1922
"github.com/hyperledger/fabric-sdk-go/third_party/github.com/hyperledger/fabric/protos/common"
2023
pb "github.com/hyperledger/fabric-sdk-go/third_party/github.com/hyperledger/fabric/protos/peer"
@@ -34,9 +37,10 @@ var logger = logging.NewLogger("fabsdk/client")
3437
// An application that requires interaction with multiple channels should create a separate
3538
// instance of the ledger client for each channel. Ledger client supports specific queries only.
3639
type Client struct {
37-
ctx context.Channel
38-
filter fab.TargetFilter
39-
ledger *channel.Ledger
40+
ctx context.Channel
41+
filter fab.TargetFilter
42+
ledger *channel.Ledger
43+
verifier *requestVerifier
4044
}
4145

4246
// mspFilter is default filter
@@ -57,14 +61,24 @@ func New(channelProvider context.ChannelProvider, opts ...ClientOption) (*Client
5761
return nil, err
5862
}
5963

64+
if channelContext.ChannelService() == nil {
65+
return nil, errors.New("channel service not initialized")
66+
}
67+
68+
membership, err := channelContext.ChannelService().Membership()
69+
if err != nil {
70+
return nil, errors.WithMessage(err, "membership creation failed")
71+
}
72+
6073
ledger, err := channel.NewLedger(channelContext.ChannelID())
6174
if err != nil {
6275
return nil, err
6376
}
6477

6578
ledgerClient := Client{
66-
ctx: channelContext,
67-
ledger: ledger,
79+
ctx: channelContext,
80+
ledger: ledger,
81+
verifier: &requestVerifier{membership: membership},
6882
}
6983

7084
for _, opt := range opts {
@@ -93,21 +107,21 @@ func (c *Client) QueryInfo(options ...RequestOption) (*fab.BlockchainInfoRespons
93107

94108
opts, err := c.prepareRequestOpts(options...)
95109
if err != nil {
96-
return nil, errors.WithMessage(err, "failed to get opts for QueryBlockByHash")
110+
return nil, errors.WithMessage(err, "failed to get opts for QueryInfo")
97111
}
98112

99113
// Determine targets
100114
targets, err := c.calculateTargets(opts)
101115
if err != nil {
102-
return nil, errors.WithMessage(err, "failed to determine target peers for QueryBlockByHash")
116+
return nil, errors.WithMessage(err, "failed to determine target peers for QueryInfo")
103117
}
104118

105119
reqCtx, cancel := c.createRequestContext(&opts)
106120
defer cancel()
107121

108-
responses, err := c.ledger.QueryInfo(reqCtx, peersToTxnProcessors(targets))
122+
responses, err := c.ledger.QueryInfo(reqCtx, peersToTxnProcessors(targets), c.verifier)
109123
if err != nil && len(responses) == 0 {
110-
return nil, errors.WithMessage(err, "Failed to QueryBlockByHash")
124+
return nil, errors.WithMessage(err, "Failed to QueryInfo")
111125
}
112126

113127
if len(responses) < opts.MinTargets {
@@ -151,7 +165,7 @@ func (c *Client) QueryBlockByHash(blockHash []byte, options ...RequestOption) (*
151165
reqCtx, cancel := c.createRequestContext(&opts)
152166
defer cancel()
153167

154-
responses, err := c.ledger.QueryBlockByHash(reqCtx, blockHash, peersToTxnProcessors(targets))
168+
responses, err := c.ledger.QueryBlockByHash(reqCtx, blockHash, peersToTxnProcessors(targets), c.verifier)
155169
if err != nil && len(responses) == 0 {
156170
return nil, errors.WithMessage(err, "Failed to QueryBlockByHash")
157171
}
@@ -195,7 +209,7 @@ func (c *Client) QueryBlock(blockNumber uint64, options ...RequestOption) (*comm
195209
reqCtx, cancel := c.createRequestContext(&opts)
196210
defer cancel()
197211

198-
responses, err := c.ledger.QueryBlock(reqCtx, blockNumber, peersToTxnProcessors(targets))
212+
responses, err := c.ledger.QueryBlock(reqCtx, blockNumber, peersToTxnProcessors(targets), c.verifier)
199213
if err != nil && len(responses) == 0 {
200214
return nil, errors.WithMessage(err, "Failed to QueryBlock")
201215
}
@@ -210,8 +224,6 @@ func (c *Client) QueryBlock(blockNumber uint64, options ...RequestOption) (*comm
210224
continue
211225
}
212226

213-
// TODO: Signature validation
214-
215227
// All payloads have to match
216228
if !proto.Equal(response.Data, r.Data) {
217229
return nil, errors.New("Payloads for QueryBlock do not match")
@@ -240,7 +252,7 @@ func (c *Client) QueryTransaction(transactionID fab.TransactionID, options ...Re
240252
reqCtx, cancel := c.createRequestContext(&opts)
241253
defer cancel()
242254

243-
responses, err := c.ledger.QueryTransaction(reqCtx, transactionID, peersToTxnProcessors(targets))
255+
responses, err := c.ledger.QueryTransaction(reqCtx, transactionID, peersToTxnProcessors(targets), c.verifier)
244256
if err != nil && len(responses) == 0 {
245257
return nil, errors.WithMessage(err, "Failed to QueryTransaction")
246258
}
@@ -255,8 +267,6 @@ func (c *Client) QueryTransaction(transactionID fab.TransactionID, options ...Re
255267
continue
256268
}
257269

258-
// TODO: Signature validation
259-
260270
// All payloads have to match
261271
if !proto.Equal(response, r) {
262272
return nil, errors.New("Payloads for QueryBlockByHash do not match")
@@ -413,3 +423,43 @@ func shuffle(a []fab.Peer) {
413423
a[i], a[j] = a[j], a[i]
414424
}
415425
}
426+
427+
type requestVerifier struct {
428+
membership fab.ChannelMembership
429+
}
430+
431+
// Verify checks transaction proposal response
432+
func (v *requestVerifier) Verify(response *fab.TransactionProposalResponse) error {
433+
434+
if response.ProposalResponse.GetResponse().Status != int32(common.Status_SUCCESS) {
435+
return status.NewFromProposalResponse(response.ProposalResponse, response.Endorser)
436+
}
437+
438+
res := response.ProposalResponse
439+
440+
if res.GetEndorsement() == nil {
441+
return errors.Errorf("Missing endorsement in proposal response")
442+
}
443+
creatorID := res.GetEndorsement().Endorser
444+
445+
err := v.membership.Validate(creatorID)
446+
if err != nil {
447+
return errors.WithMessage(err, "The creator certificate is not valid")
448+
}
449+
450+
// check the signature against the endorser and payload hash
451+
digest := append(res.GetPayload(), res.GetEndorsement().Endorser...)
452+
453+
// validate the signature
454+
err = v.membership.Verify(creatorID, digest, res.GetEndorsement().Signature)
455+
if err != nil {
456+
return errors.WithMessage(err, "The creator's signature over the proposal is not valid")
457+
}
458+
459+
return nil
460+
}
461+
462+
// Match matches transaction proposal responses (empty)
463+
func (v *requestVerifier) Match(response []*fab.TransactionProposalResponse) error {
464+
return nil
465+
}

pkg/client/resmgmt/resmgmt.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,8 @@ func (rc *Client) QueryInstantiatedChaincodes(channelID string, options ...Reque
478478
reqCtx, cancel := rc.createRequestContext(opts, core.PeerResponse)
479479
defer cancel()
480480

481-
responses, err := l.QueryInstantiatedChaincodes(reqCtx, []fab.ProposalProcessor{target})
481+
// TODO: Should we move QueryInstantiatedChaincodes to ledger client
482+
responses, err := l.QueryInstantiatedChaincodes(reqCtx, []fab.ProposalProcessor{target}, nil)
482483
if err != nil {
483484
return nil, err
484485
}

pkg/fab/channel/ledger.go

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ type Ledger struct {
3434
chName string
3535
}
3636

37+
// ResponseVerifier checks transaction proposal response(s)
38+
type ResponseVerifier interface {
39+
Verify(response *fab.TransactionProposalResponse) error
40+
Match(response []*fab.TransactionProposalResponse) error
41+
}
42+
3743
// NewLedger constructs a Ledger client for the current context and named channel.
3844
func NewLedger(chName string) (*Ledger, error) {
3945
l := Ledger{
@@ -44,11 +50,11 @@ func NewLedger(chName string) (*Ledger, error) {
4450

4551
// QueryInfo queries for various useful information on the state of the channel
4652
// (height, known peers).
47-
func (c *Ledger) QueryInfo(reqCtx reqContext.Context, targets []fab.ProposalProcessor) ([]*fab.BlockchainInfoResponse, error) {
53+
func (c *Ledger) QueryInfo(reqCtx reqContext.Context, targets []fab.ProposalProcessor, verifier ResponseVerifier) ([]*fab.BlockchainInfoResponse, error) {
4854
logger.Debug("queryInfo - start")
4955

5056
cir := createChannelInfoInvokeRequest(c.chName)
51-
tprs, errs := queryChaincode(reqCtx, c.chName, cir, targets)
57+
tprs, errs := queryChaincode(reqCtx, c.chName, cir, targets, verifier)
5258

5359
responses := []*fab.BlockchainInfoResponse{}
5460
for _, tpr := range tprs {
@@ -74,14 +80,14 @@ func createBlockchainInfo(tpr *fab.TransactionProposalResponse) (*common.Blockch
7480
// QueryBlockByHash queries the ledger for Block by block hash.
7581
// This query will be made to specified targets.
7682
// Returns the block.
77-
func (c *Ledger) QueryBlockByHash(reqCtx reqContext.Context, blockHash []byte, targets []fab.ProposalProcessor) ([]*common.Block, error) {
83+
func (c *Ledger) QueryBlockByHash(reqCtx reqContext.Context, blockHash []byte, targets []fab.ProposalProcessor, verifier ResponseVerifier) ([]*common.Block, error) {
7884

7985
if blockHash == nil {
8086
return nil, errors.New("blockHash is required")
8187
}
8288

8389
cir := createBlockByHashInvokeRequest(c.chName, blockHash)
84-
tprs, errs := queryChaincode(reqCtx, c.chName, cir, targets)
90+
tprs, errs := queryChaincode(reqCtx, c.chName, cir, targets, verifier)
8591

8692
responses := []*common.Block{}
8793
for _, tpr := range tprs {
@@ -99,10 +105,10 @@ func (c *Ledger) QueryBlockByHash(reqCtx reqContext.Context, blockHash []byte, t
99105
// This query will be made to specified targets.
100106
// blockNumber: The number which is the ID of the Block.
101107
// It returns the block.
102-
func (c *Ledger) QueryBlock(reqCtx reqContext.Context, blockNumber uint64, targets []fab.ProposalProcessor) ([]*common.Block, error) {
108+
func (c *Ledger) QueryBlock(reqCtx reqContext.Context, blockNumber uint64, targets []fab.ProposalProcessor, verifier ResponseVerifier) ([]*common.Block, error) {
103109

104110
cir := createBlockByNumberInvokeRequest(c.chName, blockNumber)
105-
tprs, errs := queryChaincode(reqCtx, c.chName, cir, targets)
111+
tprs, errs := queryChaincode(reqCtx, c.chName, cir, targets, verifier)
106112

107113
responses := []*common.Block{}
108114
for _, tpr := range tprs {
@@ -128,10 +134,10 @@ func createCommonBlock(tpr *fab.TransactionProposalResponse) (*common.Block, err
128134
// QueryTransaction queries the ledger for Transaction by number.
129135
// This query will be made to specified targets.
130136
// Returns the ProcessedTransaction information containing the transaction.
131-
func (c *Ledger) QueryTransaction(reqCtx reqContext.Context, transactionID fab.TransactionID, targets []fab.ProposalProcessor) ([]*pb.ProcessedTransaction, error) {
137+
func (c *Ledger) QueryTransaction(reqCtx reqContext.Context, transactionID fab.TransactionID, targets []fab.ProposalProcessor, verifier ResponseVerifier) ([]*pb.ProcessedTransaction, error) {
132138

133139
cir := createTransactionByIDInvokeRequest(c.chName, transactionID)
134-
tprs, errs := queryChaincode(reqCtx, c.chName, cir, targets)
140+
tprs, errs := queryChaincode(reqCtx, c.chName, cir, targets, verifier)
135141

136142
responses := []*pb.ProcessedTransaction{}
137143
for _, tpr := range tprs {
@@ -157,9 +163,9 @@ func createProcessedTransaction(tpr *fab.TransactionProposalResponse) (*pb.Proce
157163

158164
// QueryInstantiatedChaincodes queries the instantiated chaincodes on this channel.
159165
// This query will be made to specified targets.
160-
func (c *Ledger) QueryInstantiatedChaincodes(reqCtx reqContext.Context, targets []fab.ProposalProcessor) ([]*pb.ChaincodeQueryResponse, error) {
166+
func (c *Ledger) QueryInstantiatedChaincodes(reqCtx reqContext.Context, targets []fab.ProposalProcessor, verifier ResponseVerifier) ([]*pb.ChaincodeQueryResponse, error) {
161167
cir := createChaincodeInvokeRequest()
162-
tprs, errs := queryChaincode(reqCtx, c.chName, cir, targets)
168+
tprs, errs := queryChaincode(reqCtx, c.chName, cir, targets, verifier)
163169

164170
responses := []*pb.ChaincodeQueryResponse{}
165171
for _, tpr := range tprs {
@@ -184,7 +190,7 @@ func createChaincodeQueryResponse(tpr *fab.TransactionProposalResponse) (*pb.Cha
184190

185191
// QueryConfigBlock returns the current configuration block for the specified channel. If the
186192
// peer doesn't belong to the channel, return error
187-
func (c *Ledger) QueryConfigBlock(reqCtx reqContext.Context, targets []fab.ProposalProcessor, minResponses int) (*common.ConfigEnvelope, error) {
193+
func (c *Ledger) QueryConfigBlock(reqCtx reqContext.Context, targets []fab.ProposalProcessor, minResponses int, verifier ResponseVerifier) (*common.ConfigEnvelope, error) {
188194

189195
if len(targets) == 0 {
190196
return nil, errors.New("target(s) required")
@@ -195,7 +201,7 @@ func (c *Ledger) QueryConfigBlock(reqCtx reqContext.Context, targets []fab.Propo
195201
}
196202

197203
cir := createConfigBlockInvokeRequest(c.chName)
198-
tprs, err := queryChaincode(reqCtx, c.chName, cir, targets)
204+
tprs, err := queryChaincode(reqCtx, c.chName, cir, targets, verifier)
199205
if err != nil && len(tprs) == 0 {
200206
return nil, errors.WithMessage(err, "queryChaincode failed")
201207
}
@@ -242,7 +248,7 @@ func collectProposalResponses(tprs []*fab.TransactionProposalResponse) [][]byte
242248
return responses
243249
}
244250

245-
func queryChaincode(reqCtx reqContext.Context, channelID string, request fab.ChaincodeInvokeRequest, targets []fab.ProposalProcessor) ([]*fab.TransactionProposalResponse, error) {
251+
func queryChaincode(reqCtx reqContext.Context, channelID string, request fab.ChaincodeInvokeRequest, targets []fab.ProposalProcessor, verifier ResponseVerifier) ([]*fab.TransactionProposalResponse, error) {
246252
ctx, ok := contextImpl.RequestClientContext(reqCtx)
247253
if !ok {
248254
return nil, errors.New("failed get client context from reqContext for signProposal")
@@ -258,13 +264,19 @@ func queryChaincode(reqCtx reqContext.Context, channelID string, request fab.Cha
258264
}
259265
tprs, errs := txn.SendProposal(reqCtx, tp, targets)
260266

261-
return filterResponses(tprs, errs)
267+
return filterResponses(tprs, errs, verifier)
262268
}
263269

264-
func filterResponses(responses []*fab.TransactionProposalResponse, errs error) ([]*fab.TransactionProposalResponse, error) {
270+
func filterResponses(responses []*fab.TransactionProposalResponse, errs error, verifier ResponseVerifier) ([]*fab.TransactionProposalResponse, error) {
265271
filteredResponses := responses[:0]
266272
for _, response := range responses {
267273
if response.Status == http.StatusOK {
274+
if verifier != nil {
275+
if err := verifier.Verify(response); err != nil {
276+
errs = multi.Append(errs, errors.Errorf("failed to verify response from %s: %s", response.Endorser, err))
277+
continue
278+
}
279+
}
268280
filteredResponses = append(filteredResponses, response)
269281
} else {
270282
errs = multi.Append(errs, errors.Errorf("bad status from %s (%d)", response.Endorser, response.Status))

0 commit comments

Comments
 (0)