Skip to content

Commit

Permalink
[FAB-8938] Ledger Client: Signature validation
Browse files Browse the repository at this point in the history
Change-Id: I82f29e19df11f16712de7c3b649fdc06fbb6da9a
Signed-off-by: Sandra Vrtikapa <sandra.vrtikapa@securekey.com>
  • Loading branch information
sandrask committed Mar 16, 2018
1 parent cc16a4a commit b54994a
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 48 deletions.
82 changes: 66 additions & 16 deletions pkg/client/ledger/ledger.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ import (
"time"

"github.com/golang/protobuf/proto"

"github.com/hyperledger/fabric-sdk-go/pkg/common/providers/context"
"github.com/hyperledger/fabric-sdk-go/pkg/common/providers/fab"
"github.com/hyperledger/fabric-sdk-go/pkg/util/errors/status"

"github.com/hyperledger/fabric-sdk-go/pkg/fab/chconfig"
"github.com/hyperledger/fabric-sdk-go/third_party/github.com/hyperledger/fabric/protos/common"
pb "github.com/hyperledger/fabric-sdk-go/third_party/github.com/hyperledger/fabric/protos/peer"
Expand All @@ -34,9 +37,10 @@ var logger = logging.NewLogger("fabsdk/client")
// An application that requires interaction with multiple channels should create a separate
// instance of the ledger client for each channel. Ledger client supports specific queries only.
type Client struct {
ctx context.Channel
filter fab.TargetFilter
ledger *channel.Ledger
ctx context.Channel
filter fab.TargetFilter
ledger *channel.Ledger
verifier *requestVerifier
}

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

if channelContext.ChannelService() == nil {
return nil, errors.New("channel service not initialized")
}

membership, err := channelContext.ChannelService().Membership()
if err != nil {
return nil, errors.WithMessage(err, "membership creation failed")
}

ledger, err := channel.NewLedger(channelContext.ChannelID())
if err != nil {
return nil, err
}

ledgerClient := Client{
ctx: channelContext,
ledger: ledger,
ctx: channelContext,
ledger: ledger,
verifier: &requestVerifier{membership: membership},
}

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

opts, err := c.prepareRequestOpts(options...)
if err != nil {
return nil, errors.WithMessage(err, "failed to get opts for QueryBlockByHash")
return nil, errors.WithMessage(err, "failed to get opts for QueryInfo")
}

// Determine targets
targets, err := c.calculateTargets(opts)
if err != nil {
return nil, errors.WithMessage(err, "failed to determine target peers for QueryBlockByHash")
return nil, errors.WithMessage(err, "failed to determine target peers for QueryInfo")
}

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

responses, err := c.ledger.QueryInfo(reqCtx, peersToTxnProcessors(targets))
responses, err := c.ledger.QueryInfo(reqCtx, peersToTxnProcessors(targets), c.verifier)
if err != nil && len(responses) == 0 {
return nil, errors.WithMessage(err, "Failed to QueryBlockByHash")
return nil, errors.WithMessage(err, "Failed to QueryInfo")
}

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

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

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

// TODO: Signature validation

// All payloads have to match
if !proto.Equal(response.Data, r.Data) {
return nil, errors.New("Payloads for QueryBlock do not match")
Expand Down Expand Up @@ -240,7 +252,7 @@ func (c *Client) QueryTransaction(transactionID fab.TransactionID, options ...Re
reqCtx, cancel := c.createRequestContext(&opts)
defer cancel()

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

// TODO: Signature validation

// All payloads have to match
if !proto.Equal(response, r) {
return nil, errors.New("Payloads for QueryBlockByHash do not match")
Expand Down Expand Up @@ -413,3 +423,43 @@ func shuffle(a []fab.Peer) {
a[i], a[j] = a[j], a[i]
}
}

type requestVerifier struct {
membership fab.ChannelMembership
}

// Verify checks transaction proposal response
func (v *requestVerifier) Verify(response *fab.TransactionProposalResponse) error {

if response.ProposalResponse.GetResponse().Status != int32(common.Status_SUCCESS) {
return status.NewFromProposalResponse(response.ProposalResponse, response.Endorser)
}

res := response.ProposalResponse

if res.GetEndorsement() == nil {
return errors.Errorf("Missing endorsement in proposal response")
}
creatorID := res.GetEndorsement().Endorser

err := v.membership.Validate(creatorID)
if err != nil {
return errors.WithMessage(err, "The creator certificate is not valid")
}

// check the signature against the endorser and payload hash
digest := append(res.GetPayload(), res.GetEndorsement().Endorser...)

// validate the signature
err = v.membership.Verify(creatorID, digest, res.GetEndorsement().Signature)
if err != nil {
return errors.WithMessage(err, "The creator's signature over the proposal is not valid")
}

return nil
}

// Match matches transaction proposal responses (empty)
func (v *requestVerifier) Match(response []*fab.TransactionProposalResponse) error {
return nil
}
3 changes: 2 additions & 1 deletion pkg/client/resmgmt/resmgmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,8 @@ func (rc *Client) QueryInstantiatedChaincodes(channelID string, options ...Reque
reqCtx, cancel := rc.createRequestContext(opts, core.PeerResponse)
defer cancel()

responses, err := l.QueryInstantiatedChaincodes(reqCtx, []fab.ProposalProcessor{target})
// TODO: Should we move QueryInstantiatedChaincodes to ledger client
responses, err := l.QueryInstantiatedChaincodes(reqCtx, []fab.ProposalProcessor{target}, nil)
if err != nil {
return nil, err
}
Expand Down
42 changes: 27 additions & 15 deletions pkg/fab/channel/ledger.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ type Ledger struct {
chName string
}

// ResponseVerifier checks transaction proposal response(s)
type ResponseVerifier interface {
Verify(response *fab.TransactionProposalResponse) error
Match(response []*fab.TransactionProposalResponse) error
}

// NewLedger constructs a Ledger client for the current context and named channel.
func NewLedger(chName string) (*Ledger, error) {
l := Ledger{
Expand All @@ -44,11 +50,11 @@ func NewLedger(chName string) (*Ledger, error) {

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

cir := createChannelInfoInvokeRequest(c.chName)
tprs, errs := queryChaincode(reqCtx, c.chName, cir, targets)
tprs, errs := queryChaincode(reqCtx, c.chName, cir, targets, verifier)

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

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

cir := createBlockByHashInvokeRequest(c.chName, blockHash)
tprs, errs := queryChaincode(reqCtx, c.chName, cir, targets)
tprs, errs := queryChaincode(reqCtx, c.chName, cir, targets, verifier)

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

cir := createBlockByNumberInvokeRequest(c.chName, blockNumber)
tprs, errs := queryChaincode(reqCtx, c.chName, cir, targets)
tprs, errs := queryChaincode(reqCtx, c.chName, cir, targets, verifier)

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

cir := createTransactionByIDInvokeRequest(c.chName, transactionID)
tprs, errs := queryChaincode(reqCtx, c.chName, cir, targets)
tprs, errs := queryChaincode(reqCtx, c.chName, cir, targets, verifier)

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

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

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

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

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

cir := createConfigBlockInvokeRequest(c.chName)
tprs, err := queryChaincode(reqCtx, c.chName, cir, targets)
tprs, err := queryChaincode(reqCtx, c.chName, cir, targets, verifier)
if err != nil && len(tprs) == 0 {
return nil, errors.WithMessage(err, "queryChaincode failed")
}
Expand Down Expand Up @@ -242,7 +248,7 @@ func collectProposalResponses(tprs []*fab.TransactionProposalResponse) [][]byte
return responses
}

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

return filterResponses(tprs, errs)
return filterResponses(tprs, errs, verifier)
}

func filterResponses(responses []*fab.TransactionProposalResponse, errs error) ([]*fab.TransactionProposalResponse, error) {
func filterResponses(responses []*fab.TransactionProposalResponse, errs error, verifier ResponseVerifier) ([]*fab.TransactionProposalResponse, error) {
filteredResponses := responses[:0]
for _, response := range responses {
if response.Status == http.StatusOK {
if verifier != nil {
if err := verifier.Verify(response); err != nil {
errs = multi.Append(errs, errors.Errorf("failed to verify response from %s: %s", response.Endorser, err))
continue
}
}
filteredResponses = append(filteredResponses, response)
} else {
errs = multi.Append(errs, errors.Errorf("bad status from %s (%d)", response.Endorser, response.Status))
Expand Down
Loading

0 comments on commit b54994a

Please sign in to comment.