Skip to content

Commit

Permalink
enable chaincode request message consistency check
Browse files Browse the repository at this point in the history
Signed-off-by: Bruno Vavala <[email protected]>
  • Loading branch information
bvavala committed Dec 28, 2020
1 parent 1d5f85d commit 8485afd
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 1 deletion.
20 changes: 20 additions & 0 deletions ecc_enclave/enclave/enclave.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,26 @@ int ecall_cc_invoke(const uint8_t* signed_proposal_proto_bytes,
crm.has_proposal = true;
}

{ // fill chaincode request message hash
// hash request
ByteArray ba_cc_request_message(
cc_request_message_bytes, cc_request_message_bytes + cc_request_message_bytes_len);
ByteArray ba_cc_request_message_hash =
pdo::crypto::ComputeMessageHash(ba_cc_request_message);

// encode field
LOG_DEBUG("adding request hash: %s",
(ByteArrayToHexEncodedString(ba_cc_request_message_hash)).c_str());
crm.chaincode_request_message_hash = (pb_bytes_array_t*)pb_realloc(
NULL, PB_BYTES_ARRAY_T_ALLOCSIZE(ba_cc_request_message_hash.size()));
COND2LOGERR(crm.chaincode_request_message_hash == NULL, "cannot allocate request hash");
crm.chaincode_request_message_hash->size = ba_cc_request_message_hash.size();
ret = memcpy_s(crm.chaincode_request_message_hash->bytes,
crm.chaincode_request_message_hash->size, ba_cc_request_message_hash.data(),
ba_cc_request_message_hash.size());
COND2LOGERR(ret != 0, "cannot encode request hash");
}

{ // fill rwset
crm.has_fpc_rw_set = true;
rwset_to_proto(&ctx, &crm.fpc_rw_set);
Expand Down
94 changes: 94 additions & 0 deletions internal/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@ SPDX-License-Identifier: Apache-2.0
package utils

import (
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"strconv"
"strings"

"github.com/golang/protobuf/proto"
"github.com/hyperledger/fabric-protos-go/common"
pb "github.com/hyperledger/fabric-protos-go/peer"
"github.com/pkg/errors"
)

Expand Down Expand Up @@ -83,3 +87,93 @@ func ValidateEndpoint(endpoint string) error {

return nil
}

func GetChaincodeRequestMessageFromSignedProposal(signedProposal *pb.SignedProposal) (crmProtoBytes []byte, e error) {
// This function is based on the `newChaincodeStub` in
// https://github.com/hyperledger/fabric-chaincode-go/blob/9b3ae92d8664f398fe9846561fafde44864d55e3/shim/stub.go
// In particular, it serves to
// * extract the arguments from the proposal,
// * check that exactly two arguments have been passed (i.e., the function name and crm)
// * return the (base-64 decoded) chaincode request message bytes

if signedProposal == nil {
return nil, fmt.Errorf("no signed proposal to parse")
}

var err error

proposal := &pb.Proposal{}
err = proto.Unmarshal(signedProposal.ProposalBytes, proposal)
if err != nil {
return nil, fmt.Errorf("failed to extract Proposal from SignedProposal: %s", err)
}

// check for header
if len(proposal.GetHeader()) == 0 {
return nil, errors.New("failed to extract Proposal fields: proposal header is nil")
}

// extract header
hdr := &common.Header{}
if err := proto.Unmarshal(proposal.GetHeader(), hdr); err != nil {
return nil, fmt.Errorf("failed to extract proposal header: %s", err)
}

// validate channel header
chdr := &common.ChannelHeader{}
if err := proto.Unmarshal(hdr.ChannelHeader, chdr); err != nil {
return nil, fmt.Errorf("failed to extract channel header: %s", err)
}
validTypes := map[common.HeaderType]bool{
common.HeaderType_ENDORSER_TRANSACTION: true,
}
if !validTypes[common.HeaderType(chdr.GetType())] {
return nil, fmt.Errorf(
"invalid channel header type. Expected %s, received %s",
common.HeaderType_ENDORSER_TRANSACTION,
common.HeaderType(chdr.GetType()),
)
}

// extract args from proposal payload
payload := &pb.ChaincodeProposalPayload{}
if err := proto.Unmarshal(proposal.GetPayload(), payload); err != nil {
return nil, fmt.Errorf("failed to extract proposal payload: %s", err)
}
cppInput := payload.GetInput()
if cppInput == nil {
return nil, fmt.Errorf("failed to get chaincode proposal payload input")
}
chaincodeInvocationSpec := &pb.ChaincodeInvocationSpec{}
err = proto.Unmarshal(cppInput, chaincodeInvocationSpec)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal chaincodeInvocationSpec: %s", err)
}
chaincodeSpec := chaincodeInvocationSpec.GetChaincodeSpec()
if chaincodeSpec == nil {
return nil, fmt.Errorf("failed to get chaincode spec")
}
input := chaincodeSpec.GetInput()
if input == nil {
return nil, fmt.Errorf("failed to get chaincode spec input")
}
args := input.GetArgs()
if args == nil {
return nil, fmt.Errorf("failed to get chaincode spec input args")
}

// validate args
// there two args:
// 1. the function name (usually "__invoke") and
// 2. the b64-encoded chaincode request message bytes
if len(args) != 2 {
return nil, fmt.Errorf("unexpected args num %d instead of 2 (function + chaincode request message", len(args))
}

// Return the decoded second arg
chaincodeRequestMessageBytes, err := base64.StdEncoding.DecodeString(string(args[1]))
if err != nil {
return nil, fmt.Errorf("failed to decode chaincode request message")
}
return chaincodeRequestMessageBytes, nil
}
26 changes: 26 additions & 0 deletions internal/utils/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (
"github.com/hyperledger-labs/fabric-private-chaincode/internal/protos"
"github.com/hyperledger/fabric-chaincode-go/shim"
"github.com/hyperledger/fabric/common/flogging"
"google.golang.org/protobuf/proto"
"strings"
)

// #cgo CFLAGS: -I${SRCDIR}/../../common/crypto
Expand Down Expand Up @@ -141,5 +143,29 @@ func Validate(signedResponseMessage *protos.SignedChaincodeResponseMessage, atte
return fmt.Errorf("enclave signature verification failed")
}

// verify signed proposal input hash matches input hash
chaincodeResponseMessage := &protos.ChaincodeResponseMessage{}
if err := proto.Unmarshal(signedResponseMessage.GetChaincodeResponseMessage(), chaincodeResponseMessage); err != nil {
return fmt.Errorf("failed to extract response message: %s", err)
}
originalSignedProposal := chaincodeResponseMessage.GetProposal()
if originalSignedProposal == nil {
return fmt.Errorf("cannot get the signed proposal that the enclave received")
}
chaincodeRequestMessageBytes, err := GetChaincodeRequestMessageFromSignedProposal(originalSignedProposal)
if err != nil {
return fmt.Errorf("failed to extract chaincode request message: %s", err)
}
expectedChaincodeRequestMessageHash := sha256.Sum256(chaincodeRequestMessageBytes)
chaincodeRequestMessageHash := chaincodeResponseMessage.GetChaincodeRequestMessageHash()
if chaincodeRequestMessageHash == nil {
return fmt.Errorf("cannot get the chaincode request message hash")
}
if !bytes.Equal(expectedChaincodeRequestMessageHash[:], chaincodeRequestMessageHash) {
logger.Debugf("expected chaincode request message hash: %s", strings.ToUpper(hex.EncodeToString(expectedChaincodeRequestMessageHash[:])))
logger.Debugf("received chaincode request message hash: %s", strings.ToUpper(hex.EncodeToString(chaincodeRequestMessageHash[:])))
return fmt.Errorf("chaincode request message hash mismatch")
}

return nil
}
1 change: 1 addition & 0 deletions protos/fpc.options
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ fpc.ChaincodeRequestMessage.encrypted_request type:FT_POINTER
fpc.FPCKVSet.read_value_hashes type:FT_POINTER

fpc.ChaincodeResponseMessage.encrypted_response type:FT_POINTER
fpc.ChaincodeResponseMessage.chaincode_request_message_hash type:FT_POINTER
fpc.ChaincodeResponseMessage.enclave_id type:FT_POINTER

fpc.SignedChaincodeResponseMessage.chaincode_response_message type:FT_POINTER
Expand Down
7 changes: 6 additions & 1 deletion protos/fpc/fpc.proto
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,13 @@ message ChaincodeResponseMessage {
// signed proposal for this request
protos.SignedProposal proposal = 3;

// hash of the proposal's input request
// this field is required because input request is passed alongside the proposal
// and not extracted from it; validation chaincode will check for consistency
bytes chaincode_request_message_hash = 4;

// identity for public key used to sign
string enclave_id = 4;
string enclave_id = 5;
}

message SignedChaincodeResponseMessage {
Expand Down

0 comments on commit 8485afd

Please sign in to comment.