Skip to content

Commit

Permalink
Integration of MSP in endorser
Browse files Browse the repository at this point in the history
This code change integrates the MSP into the peer, endorser and ESCC. Currently
the MSP is used to validate the proposal (in the endorser) and endorse it (in
the ESCC). Also, the peer CLI uses the MSP to sign the proposal to be sent
to the endorser. Two MSP implementations are available: a no-op and one that
uses BCCSP (and golang native x509 support) to handle x509 certificates.

Missing:
 * loading the roots of trust from the genesis block using a CSCC call;
 * loading more than one identity;
 * specifying the location of the config file in the .yaml config;
 * a way of identifying signing identities for the CLI;

NOTE: this commit also contains some code from
 - https://gerrit.hyperledger.org/r/#/c/2243/
 - https://gerrit.hyperledger.org/r/#/c/2385/
 - https://gerrit.hyperledger.org/r/#/c/2465/

Change-Id: I831b7bd01eb8b71cd3a6d9893a4faf6f7fa90fed
Signed-off-by: Alessandro Sorniotti <ale.linux@sopit.net>
Signed-off-by: Elli Androulaki <lli@zurich.ibm.com>
  • Loading branch information
ale-linux committed Nov 17, 2016
1 parent af5285a commit 5f98d54
Show file tree
Hide file tree
Showing 27 changed files with 1,722 additions and 71 deletions.
11 changes: 10 additions & 1 deletion bddtests/endorser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/DATA-DOG/godog/gherkin"
"github.com/hyperledger/fabric/core/util"
pb "github.com/hyperledger/fabric/protos/peer"
"github.com/hyperledger/fabric/protos/utils"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
Expand Down Expand Up @@ -243,8 +244,16 @@ func (b *BDDContext) userSendsProposalToEndorsersWithTimeoutOfSeconds(enrollID,
}
defer grpcClient.Close()

proposalBytes, err := utils.GetBytesProposal(proposal)
if err != nil {
respQueue <- &KeyedProposalResponse{endorser, nil, fmt.Errorf("Error serializing proposal bytes")}
return
}
// FIXME: the endorser needs to be given a signed proposal - who should sign?
signedProposal := &pb.SignedProposal{ProposalBytes: proposalBytes, Signature: []byte("signature")}

endorserClient := pb.NewEndorserClient(grpcClient)
if proposalResponse, localErr = endorserClient.ProcessProposal(ctx, proposal); localErr != nil {
if proposalResponse, localErr = endorserClient.ProcessProposal(ctx, signedProposal); localErr != nil {
respQueue <- &KeyedProposalResponse{endorser, nil, fmt.Errorf("Error calling endorser '%s': %s", endorser, localErr)}
return
}
Expand Down
2 changes: 1 addition & 1 deletion core/chaincode/importsysccs.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ var systemChaincodes = []*SystemChaincode{
Enabled: true,
Name: "escc",
Path: "github.com/hyperledger/fabric/core/system_chaincode/escc",
InitArgs: [][]byte{[]byte("")},
InitArgs: [][]byte{[]byte("DEFAULT"), []byte("PEER")}, // TODO: retrieve these aruments properly
Chaincode: &escc.EndorserOneValidSignature{},
},
{
Expand Down
10 changes: 10 additions & 0 deletions core/crypto/bccsp/sw/ecdsakey.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ type ecdsaPrivateKey struct {
k *ecdsa.PrivateKey
}

// FIXME: remove as soon as there's a way to import the key more properly
func NewEcdsaPrivateKey(k *ecdsa.PrivateKey) bccsp.Key {
return &ecdsaPrivateKey{k: k}
}

// Bytes converts this key to its byte representation,
// if this operation is allowed.
func (k *ecdsaPrivateKey) Bytes() (raw []byte, err error) {
Expand Down Expand Up @@ -64,6 +69,11 @@ type ecdsaPublicKey struct {
k *ecdsa.PublicKey
}

// FIXME: remove as soon as there's a way to import the key more properly
func NewEcdsaPublicKey(k *ecdsa.PublicKey) bccsp.Key {
return &ecdsaPublicKey{k: k}
}

// Bytes converts this key to its byte representation,
// if this operation is allowed.
func (k *ecdsaPublicKey) Bytes() (raw []byte, err error) {
Expand Down
86 changes: 55 additions & 31 deletions core/endorser/endorser.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ import (
"github.com/hyperledger/fabric/core/ledger"
"github.com/hyperledger/fabric/core/ledger/kvledger"
"github.com/hyperledger/fabric/core/peer"
"github.com/hyperledger/fabric/msp"
"github.com/hyperledger/fabric/protos/common"
pb "github.com/hyperledger/fabric/protos/peer"
putils "github.com/hyperledger/fabric/protos/utils"
)

var devopsLogger = logging.MustGetLogger("endorser")
var endorserLogger = logging.MustGetLogger("endorser")

// The Jira issue that documents Endorser flow along with its relationship to
// the lifecycle chaincode - https://jira.hyperledger.org/browse/FAB-181
Expand Down Expand Up @@ -175,7 +176,7 @@ func (e *Endorser) getCDSFromLCCC(ctx context.Context, chaincodeID string, txsim

//endorse the proposal by calling the ESCC
func (e *Endorser) endorseProposal(ctx context.Context, proposal *pb.Proposal, simRes []byte, event *pb.ChaincodeEvent, visibility []byte, ccid *pb.ChaincodeID, txsim ledger.TxSimulator) ([]byte, error) {
devopsLogger.Infof("endorseProposal starts for proposal %p, simRes %p event %p, visibility %p, ccid %s", proposal, simRes, event, visibility, ccid)
endorserLogger.Infof("endorseProposal starts for proposal %p, simRes %p event %p, visibility %p, ccid %s", proposal, simRes, event, visibility, ccid)

// 1) extract the chaincodeDeploymentSpec for the chaincode we are invoking; we need it to get the escc
var escc string
Expand All @@ -197,7 +198,7 @@ func (e *Endorser) endorseProposal(ctx context.Context, proposal *pb.Proposal, s
escc = "escc"
}

devopsLogger.Infof("endorseProposal info: escc for cid %s is %s", ccid, escc)
endorserLogger.Infof("endorseProposal info: escc for cid %s is %s", ccid, escc)

// marshalling event bytes
var err error
Expand Down Expand Up @@ -239,15 +240,15 @@ func (e *Endorser) endorseProposal(ctx context.Context, proposal *pb.Proposal, s
// FIXME: this method might be of general interest, should we package it somewhere else?
// validateChaincodeProposalMessage checks the validity of a CHAINCODE Proposal message
func (e *Endorser) validateChaincodeProposalMessage(prop *pb.Proposal, hdr *common.Header) (*pb.ChaincodeHeaderExtension, error) {
devopsLogger.Infof("validateChaincodeProposalMessage starts for proposal %p, header %p", prop, hdr)
endorserLogger.Infof("validateChaincodeProposalMessage starts for proposal %p, header %p", prop, hdr)

// 4) based on the header type (assuming it's CHAINCODE), look at the extensions
chaincodeHdrExt, err := putils.GetChaincodeHeaderExtension(hdr)
if err != nil {
return nil, fmt.Errorf("Invalid header extension for type CHAINCODE")
}

devopsLogger.Infof("validateChaincodeProposalMessage info: header extension references chaincode %s", chaincodeHdrExt.ChaincodeID)
endorserLogger.Infof("validateChaincodeProposalMessage info: header extension references chaincode %s", chaincodeHdrExt.ChaincodeID)

// - ensure that the chaincodeID is correct (?)
// TODO: should we even do this? If so, using which interface?
Expand All @@ -264,65 +265,88 @@ func (e *Endorser) validateChaincodeProposalMessage(prop *pb.Proposal, hdr *comm
// validateProposalMessage checks the validity of a generic Proposal message
// this function returns Header and ChaincodeHeaderExtension messages since they
// have been unmarshalled and validated
func (e *Endorser) validateProposalMessage(prop *pb.Proposal) (*common.Header, *pb.ChaincodeHeaderExtension, error) {
devopsLogger.Infof("validateProposalMessage starts for proposal %p", prop)
func (e *Endorser) validateProposalMessage(signedProp *pb.SignedProposal) (*pb.Proposal, *common.Header, *pb.ChaincodeHeaderExtension, error) {
endorserLogger.Infof("validateProposalMessage starts for signed proposal %p", signedProp)

// extract the Proposal message from signedProp
prop, err := putils.GetProposal(signedProp.ProposalBytes)
if err != nil {
return nil, nil, nil, err
}

// 1) look at the ProposalHeader
hdr, err := putils.GetHeader(prop)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}

// - validate the type
if hdr.ChainHeader.Type != int32(common.HeaderType_ENDORSER_TRANSACTION) {
return nil, nil, fmt.Errorf("Invalid proposal type %d", hdr.ChainHeader.Type)
}
// TODO: validate the type

endorserLogger.Infof("validateProposalMessage info: proposal type %d", hdr.ChainHeader.Type)

devopsLogger.Infof("validateProposalMessage info: proposal type %d", hdr.ChainHeader.Type)
// - ensure that the version is what we expect
// TODO: Which is the right version?

// - ensure that the chainID is valid
// TODO: which set of APIs is supposed to give us this info?

// - ensure that there is a nonce and a creator
// ensure that there is a nonce and a creator
if hdr.SignatureHeader.Nonce == nil || len(hdr.SignatureHeader.Nonce) == 0 {
return nil, nil, fmt.Errorf("Invalid nonce specified in the header")
return nil, nil, nil, fmt.Errorf("Invalid nonce specified in the header")
}
if hdr.SignatureHeader.Creator == nil || len(hdr.SignatureHeader.Creator) == 0 {
return nil, nil, fmt.Errorf("Invalid creator specified in the header")
return nil, nil, nil, fmt.Errorf("Invalid creator specified in the header")
}

// - ensure that creator is a valid certificate (depends on membership svc)
// TODO: We need MSP APIs for this
// get the identity of the creator
creator, err := msp.GetManager().DeserializeIdentity(hdr.SignatureHeader.Creator)
if err != nil {
return nil, nil, nil, fmt.Errorf("Failed to deserialize creator identity, err %s", err)
}

// ensure that creator is a valid certificate
valid, err := creator.Validate()
if err != nil {
return nil, nil, nil, fmt.Errorf("Could not determine whether the identity is valid, err %s", err)
} else if !valid {
return nil, nil, nil, fmt.Errorf("The creator certificate is not valid, aborting")
}

// get the identifier and log info on the creator
identifier := creator.Identifier()
endorserLogger.Infof("validateProposalMessage info: creator identity is %s", identifier)

// - ensure that creator is trusted (signed by a trusted CA)
// TODO: We need MSP APIs for this

// - ensure that creator can transact with us (some ACLs?)
// TODO: which set of APIs is supposed to give us this info?

// - ensure that the version is what we expect
// TODO: Which is the right version?

// - ensure that the chainID is valid
// TODO: which set of APIs is supposed to give us this info?
// 2) validate the signature of creator on header and payload
verified, err := creator.Verify(signedProp.ProposalBytes, signedProp.Signature)
if err != nil {
return nil, nil, nil, fmt.Errorf("Could not determine whether the signature is valid, err %s", err)
} else if !verified {
return nil, nil, nil, fmt.Errorf("The creator's signature over the proposal is not valid, aborting")
}

// 2) perform a check against replay attacks
// 3) perform a check against replay attacks
// TODO

// 3) validate the signature of creator on header and payload
// TODO: We need MSP APIs for this

// validation of the proposal message knowing it's of type CHAINCODE
chaincodeHdrExt, err := e.validateChaincodeProposalMessage(prop, hdr)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}

return hdr, chaincodeHdrExt, err
return prop, hdr, chaincodeHdrExt, err
}

// ProcessProposal process the Proposal
func (e *Endorser) ProcessProposal(ctx context.Context, prop *pb.Proposal) (*pb.ProposalResponse, error) {
func (e *Endorser) ProcessProposal(ctx context.Context, signedProp *pb.SignedProposal) (*pb.ProposalResponse, error) {
// at first, we check whether the message is valid
// TODO: Do the checks performed by this function belong here or in the ESCC? From a security standpoint they should be performed as early as possible so here seems to be a good place
_, hdrExt, err := e.validateProposalMessage(prop)
prop, _, hdrExt, err := e.validateProposalMessage(signedProp)
if err != nil {
return &pb.ProposalResponse{Response: &pb.Response2{Status: 500, Message: err.Error()}}, err
}
Expand Down
70 changes: 62 additions & 8 deletions core/endorser/endorser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/hyperledger/fabric/core/ledger/kvledger"
"github.com/hyperledger/fabric/core/peer"
"github.com/hyperledger/fabric/core/util"
"github.com/hyperledger/fabric/msp"
pb "github.com/hyperledger/fabric/protos/peer"
pbutils "github.com/hyperledger/fabric/protos/utils"
"github.com/spf13/viper"
Expand All @@ -44,6 +45,8 @@ import (

var testDBWrapper = db.NewTestDBWrapper()
var endorserServer pb.EndorserServer
var mspInstance msp.PeerMSP
var signer msp.SigningIdentity

//initialize peer and start up. If security==enabled, login as vp
func initPeer() (net.Listener, error) {
Expand Down Expand Up @@ -120,13 +123,13 @@ func closeListenerAndSleep(l net.Listener) {

//getProposal gets the proposal for the chaincode invocation
//Currently supported only for Invokes (Queries still go through devops client)
func getProposal(cis *pb.ChaincodeInvocationSpec) (*pb.Proposal, error) {
return pbutils.CreateChaincodeProposal(cis, []byte("cert"))
func getProposal(cis *pb.ChaincodeInvocationSpec, creator []byte) (*pb.Proposal, error) {
return pbutils.CreateChaincodeProposal(cis, creator)
}

//getDeployProposal gets the proposal for the chaincode deployment
//the payload is a ChaincodeDeploymentSpec
func getDeployProposal(cds *pb.ChaincodeDeploymentSpec) (*pb.Proposal, error) {
func getDeployProposal(cds *pb.ChaincodeDeploymentSpec, creator []byte) (*pb.Proposal, error) {
b, err := proto.Marshal(cds)
if err != nil {
return nil, err
Expand All @@ -136,7 +139,21 @@ func getDeployProposal(cds *pb.ChaincodeDeploymentSpec) (*pb.Proposal, error) {
lcccSpec := &pb.ChaincodeInvocationSpec{ChaincodeSpec: &pb.ChaincodeSpec{Type: pb.ChaincodeSpec_GOLANG, ChaincodeID: &pb.ChaincodeID{Name: "lccc"}, CtorMsg: &pb.ChaincodeInput{Args: [][]byte{[]byte("deploy"), []byte("default"), b}}}}

//...and get the proposal for it
return getProposal(lcccSpec)
return getProposal(lcccSpec, creator)
}

func getSignedProposal(prop *pb.Proposal, signer msp.SigningIdentity) (*pb.SignedProposal, error) {
propBytes, err := pbutils.GetBytesProposal(prop)
if err != nil {
return nil, err
}

signature, err := signer.Sign(propBytes)
if err != nil {
return nil, err
}

return &pb.SignedProposal{ProposalBytes: propBytes, Signature: signature}, nil
}

func getDeploymentSpec(context context.Context, spec *pb.ChaincodeSpec) (*pb.ChaincodeDeploymentSpec, error) {
Expand All @@ -162,28 +179,50 @@ func deploy(endorserServer pb.EndorserServer, spec *pb.ChaincodeSpec, f func(*pb
f(depSpec)
}

creator, err := signer.Serialize()
if err != nil {
return nil, err
}

var prop *pb.Proposal
prop, err = getDeployProposal(depSpec)
prop, err = getDeployProposal(depSpec, creator)
if err != nil {
return nil, err
}

var signedProp *pb.SignedProposal
signedProp, err = getSignedProposal(prop, signer)
if err != nil {
return nil, err
}

var resp *pb.ProposalResponse
resp, err = endorserServer.ProcessProposal(context.Background(), prop)
resp, err = endorserServer.ProcessProposal(context.Background(), signedProp)

return resp, err
}

func invoke(spec *pb.ChaincodeSpec) (*pb.ProposalResponse, error) {
invocation := &pb.ChaincodeInvocationSpec{ChaincodeSpec: spec}

creator, err := signer.Serialize()
if err != nil {
return nil, err
}

var prop *pb.Proposal
prop, err := getProposal(invocation)
prop, err = getProposal(invocation, creator)
if err != nil {
return nil, fmt.Errorf("Error creating proposal %s: %s\n", spec.ChaincodeID, err)
}

resp, err := endorserServer.ProcessProposal(context.Background(), prop)
var signedProp *pb.SignedProposal
signedProp, err = getSignedProposal(prop, signer)
if err != nil {
return nil, err
}

resp, err := endorserServer.ProcessProposal(context.Background(), signedProp)
if err != nil {
return nil, fmt.Errorf("Error endorsing %s: %s\n", spec.ChaincodeID, err)
}
Expand Down Expand Up @@ -324,6 +363,21 @@ func TestMain(m *testing.M) {
}

endorserServer = NewEndorserServer(nil)

// setup the MSP manager so that we can sign/verify
mspMgrConfigFile := "../../msp/peer-config.json"
msp.GetManager().Setup(mspMgrConfigFile)
mspId := "DEFAULT"
id := "PEER"
signingIdentity := &msp.IdentityIdentifier{Mspid: msp.ProviderIdentifier{Value: mspId}, Value: id}
signer, err = msp.GetManager().GetSigningIdentity(signingIdentity)
if err != nil {
os.Exit(-1)
fmt.Printf("Could not initialize msp/signer")
finitPeer(lis)
return
}

retVal := m.Run()

finitPeer(lis)
Expand Down
3 changes: 2 additions & 1 deletion core/endorser/endorser_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,14 @@ logging:
# info - Set default to INFO
# warning:main,db=debug:chaincode=info - Override default WARNING in main,db,chaincode
# chaincode=info:main=debug:db=debug:warning - Same as above
msp: debug
peer: debug
crypto: info
status: warning
stop: warning
login: warning
vm: warning
chaincode: warning
chaincode: error


###############################################################################
Expand Down

0 comments on commit 5f98d54

Please sign in to comment.