diff --git a/ecc_enclave/enclave/enclave.cpp b/ecc_enclave/enclave/enclave.cpp index d951ade4f..c9068ccb6 100644 --- a/ecc_enclave/enclave/enclave.cpp +++ b/ecc_enclave/enclave/enclave.cpp @@ -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); diff --git a/internal/utils/utils.go b/internal/utils/utils.go index fafbbcd98..a21bea473 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -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" ) @@ -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 +} diff --git a/internal/utils/validation.go b/internal/utils/validation.go index db632f846..f004f28fa 100644 --- a/internal/utils/validation.go +++ b/internal/utils/validation.go @@ -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 @@ -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 } diff --git a/protos/fpc.options b/protos/fpc.options index a790e65fb..fc2c00e71 100644 --- a/protos/fpc.options +++ b/protos/fpc.options @@ -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 diff --git a/protos/fpc/fpc.proto b/protos/fpc/fpc.proto index e46b73efa..f6a878021 100644 --- a/protos/fpc/fpc.proto +++ b/protos/fpc/fpc.proto @@ -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 {