From 16162779cce7d5329e39435170405e13ebc2e3f9 Mon Sep 17 00:00:00 2001 From: Srinivasan Muralidharan Date: Sun, 9 Apr 2017 22:11:17 -0400 Subject: [PATCH] [FAB-2928] link installation to instantiation [part-2] This adds package specific signature to LCCC on instantiations. It also prevents launching of chaincodes if the installed chaincode does not match the signature of the instanitated chaincode. In particular, it ensures future installs that don't comply with the corresponding instantiation will be useless (cannot invoke them).. Also refactored getChaincode function per Binh's suggestion in Part-1. Change-Id: I107c91a80002a336e42aff95d4aed7efe4141ead Signed-off-by: Srinivasan Muralidharan --- core/chaincode/chaincode_support.go | 22 ++- core/chaincode/upgrade_test.go | 3 + core/common/ccprovider/ccprovider.go | 35 +++- core/common/ccprovider/cdspackage.go | 113 ++++++++---- core/common/ccprovider/cdspackage_test.go | 6 +- core/common/ccprovider/sigcdspackage.go | 106 ++++++++--- core/common/ccprovider/sigcdspackage_test.go | 6 +- core/scc/lccc/lccc.go | 184 +++++++++++++------ core/scc/lccc/lccc_test.go | 86 ++++++++- 9 files changed, 421 insertions(+), 140 deletions(-) diff --git a/core/chaincode/chaincode_support.go b/core/chaincode/chaincode_support.go index d7f13019fb7..91c5e9e86d7 100644 --- a/core/chaincode/chaincode_support.go +++ b/core/chaincode/chaincode_support.go @@ -537,6 +537,7 @@ func (chaincodeSupport *ChaincodeSupport) Launch(context context.Context, cccid var depPayload []byte //hopefully we are restarting from existing image and the deployed transaction exists + //this will also validate the ID from the LCCC depPayload, err = GetCDSFromLCCC(context, cccid.TxID, cccid.SignedProposal, cccid.Proposal, cccid.ChainID, cID.Name) if err != nil { return cID, cMsg, fmt.Errorf("Could not get deployment transaction from LCCC for %s - %s", canName, err) @@ -558,21 +559,24 @@ func (chaincodeSupport *ChaincodeSupport) Launch(context context.Context, cccid //launch container if it is a System container or not in dev mode if (!chaincodeSupport.userRunsCC || cds.ExecEnv == pb.ChaincodeDeploymentSpec_SYSTEM) && (chrte == nil || chrte.handler == nil) { - //whether we deploying, upgrading or launching a chaincode we now have a - //deployment package. If lauching, we got it from LCCC and has gone through - //ccprovider.GetChaincodeFromFS + //NOTE-We need to streamline code a bit so the data from LCCC gets passed to this thus + //avoiding the need to go to the FS. In particular, we should use cdsfs completely. It is + //just a vestige of old protocol that we continue to use ChaincodeDeploymentSpec for + //anything other than Install. In particular, instantiate, invoke, upgrade should be using + //just some form of ChaincodeInvocationSpec. + // + //But for now, if we are invoking we have gone through the LCCC path above. If instantiating + //or upgrading currently we send a CDS with nil CodePackage. In this case the codepath + //in the endorser has gone through LCCC validation. Just get the code from the FS. if cds.CodePackage == nil { //no code bytes for these situations if !(chaincodeSupport.userRunsCC || cds.ExecEnv == pb.ChaincodeDeploymentSpec_SYSTEM) { - _, cdsfs, err := ccprovider.GetChaincodeFromFS(cID.Name, cID.Version) + ccpack, err := ccprovider.GetChaincodeFromFS(cID.Name, cID.Version) if err != nil { return cID, cMsg, err } - //we should use cdsfs completely. It is just a vestige of old protocol that we - //continue to use ChaincodeDeploymentSpec for anything other than Install. In - //particular, instantiate, invoke, upgrade should be using just some form of - //ChaincodeInvocationSpec. - cds = cdsfs + + cds = ccpack.GetDepSpec() chaincodeLogger.Debugf("launchAndWaitForRegister fetched %d from file system", len(cds.CodePackage), err) } } diff --git a/core/chaincode/upgrade_test.go b/core/chaincode/upgrade_test.go index df4cb203f1a..808a764c407 100644 --- a/core/chaincode/upgrade_test.go +++ b/core/chaincode/upgrade_test.go @@ -80,6 +80,9 @@ func upgrade2(ctx context.Context, cccid *ccprovider.CCContext, } }() + //ignore existence errors + ccprovider.PutChaincodeIntoFS(chaincodeDeploymentSpec) + sysCCVers := util.GetSysCCVersion() lcccid := ccprovider.NewCCContext(cccid.ChainID, cis.ChaincodeSpec.ChaincodeId.Name, sysCCVers, uuid, true, nil, nil) diff --git a/core/common/ccprovider/ccprovider.go b/core/common/ccprovider/ccprovider.go index e4a46e5535c..fc2cc376d04 100644 --- a/core/common/ccprovider/ccprovider.go +++ b/core/common/ccprovider/ccprovider.go @@ -47,19 +47,28 @@ type CCPackage interface { // InitFromFS gets the chaincode from the filesystem (includes the raw bytes too) InitFromFS(ccname string, ccversion string) ([]byte, *pb.ChaincodeDeploymentSpec, error) + // PutChaincodeToFS writes the chaincode to the filesystem + PutChaincodeToFS() error + // GetDepSpec gets the ChaincodeDeploymentSpec from the package GetDepSpec() *pb.ChaincodeDeploymentSpec - // PutChaincodeToFS writes the chaincode to the filesystem - PutChaincodeToFS() error + // GetDepSpecBytes gets the serialized ChaincodeDeploymentSpec from the package + GetDepSpecBytes() []byte // ValidateCC validates and returns the chaincode deployment spec corresponding to // ChaincodeData. The validation is based on the metadata from ChaincodeData // One use of this method is to validate the chaincode before launching - ValidateCC(ccdata *ChaincodeData) (*pb.ChaincodeDeploymentSpec, error) + ValidateCC(ccdata *ChaincodeData) error // GetPackageObject gets the object as a proto.Message GetPackageObject() proto.Message + + // GetChaincodeData gets the ChaincodeData + GetChaincodeData() *ChaincodeData + + // GetId gets the fingerprint of the chaincode based on package computation + GetId() []byte } //SetChaincodesPath sets the chaincode path for this peer @@ -91,19 +100,20 @@ func GetChaincodePackage(ccname string, ccversion string) ([]byte, error) { } // GetChaincodeFromFS this is a wrapper for hiding package implementation. -func GetChaincodeFromFS(ccname string, ccversion string) ([]byte, *pb.ChaincodeDeploymentSpec, error) { +func GetChaincodeFromFS(ccname string, ccversion string) (CCPackage, error) { //try raw CDS cccdspack := &CDSPackage{} - b, depSpec, err := cccdspack.InitFromFS(ccname, ccversion) + _, _, err := cccdspack.InitFromFS(ccname, ccversion) if err != nil { //try signed CDS ccscdspack := &SignedCDSPackage{} - b, depSpec, err = ccscdspack.InitFromFS(ccname, ccversion) + _, _, err = ccscdspack.InitFromFS(ccname, ccversion) if err != nil { - return nil, nil, err + return nil, err } + return ccscdspack, nil } - return b, depSpec, nil + return cccdspack, nil } // PutChaincodeIntoFS is a wrapper for putting raw ChaincodeDeploymentSpec @@ -158,7 +168,7 @@ func GetInstalledChaincodes() (*pb.ChaincodeQueryResponse, error) { if len(fileNameArray) == 2 { ccname := fileNameArray[0] ccversion := fileNameArray[1] - _, cdsfs, err := GetChaincodeFromFS(ccname, ccversion) + ccpack, err := GetChaincodeFromFS(ccname, ccversion) if err != nil { // either chaincode on filesystem has been tampered with or // a non-chaincode file has been found in the chaincodes directory @@ -166,6 +176,8 @@ func GetInstalledChaincodes() (*pb.ChaincodeQueryResponse, error) { continue } + cdsfs := ccpack.GetDepSpec() + name := cdsfs.GetChaincodeSpec().GetChaincodeId().Name version := cdsfs.GetChaincodeSpec().GetChaincodeId().Version if name != ccname || version != ccversion { @@ -273,6 +285,11 @@ type ChaincodeData struct { //Data data specific to the package Data []byte `protobuf:"bytes,6,opt,name=data,proto3"` + + //Id of the chaincode that's the unique fingerprint for the CC + //This is not currently used anywhere but serves as a good + //eyecatcher + Id []byte `protobuf:"bytes,7,opt,name=id,proto3"` } //implement functions needed from proto.Message for proto's mar/unmarshal functions diff --git a/core/common/ccprovider/cdspackage.go b/core/common/ccprovider/cdspackage.go index 4a9e1e2e200..6028bf141ac 100644 --- a/core/common/ccprovider/cdspackage.go +++ b/core/common/ccprovider/cdspackage.go @@ -66,19 +66,61 @@ type CDSPackage struct { buf []byte depSpec *pb.ChaincodeDeploymentSpec data *CDSData + datab []byte + id []byte +} + +// resets data +func (ccpack *CDSPackage) reset() { + *ccpack = CDSPackage{} +} + +// GetId gets the fingerprint of the chaincode based on package computation +func (ccpack *CDSPackage) GetId() []byte { + //this has to be after creating a package and initializing it + //If those steps fail, GetId() should never be called + if ccpack.id == nil { + panic("GetId called on uninitialized package") + } + return ccpack.id } // GetDepSpec gets the ChaincodeDeploymentSpec from the package func (ccpack *CDSPackage) GetDepSpec() *pb.ChaincodeDeploymentSpec { + //this has to be after creating a package and initializing it + //If those steps fail, GetDepSpec() should never be called + if ccpack.depSpec == nil { + panic("GetDepSpec called on uninitialized package") + } return ccpack.depSpec } +// GetDepSpecBytes gets the serialized ChaincodeDeploymentSpec from the package +func (ccpack *CDSPackage) GetDepSpecBytes() []byte { + //this has to be after creating a package and initializing it + //If those steps fail, GetDepSpecBytes() should never be called + if ccpack.buf == nil { + panic("GetDepSpecBytes called on uninitialized package") + } + return ccpack.buf +} + // GetPackageObject gets the ChaincodeDeploymentSpec as proto.Message func (ccpack *CDSPackage) GetPackageObject() proto.Message { return ccpack.depSpec } -func (ccpack *CDSPackage) getCDSData(cds *pb.ChaincodeDeploymentSpec) ([]byte, *CDSData, error) { +// GetChaincodeData gets the ChaincodeData +func (ccpack *CDSPackage) GetChaincodeData() *ChaincodeData { + //this has to be after creating a package and initializing it + //If those steps fail, GetChaincodeData() should never be called + if ccpack.depSpec == nil || ccpack.datab == nil || ccpack.id == nil { + panic("GetChaincodeData called on uninitialized package") + } + return &ChaincodeData{Name: ccpack.depSpec.ChaincodeSpec.ChaincodeId.Name, Version: ccpack.depSpec.ChaincodeSpec.ChaincodeId.Version, Data: ccpack.datab, Id: ccpack.id} +} + +func (ccpack *CDSPackage) getCDSData(cds *pb.ChaincodeDeploymentSpec) ([]byte, []byte, *CDSData, error) { // check for nil argument. It is an assertion that getCDSData // is never called on a package that did not go through/succeed // package initialization. @@ -88,17 +130,17 @@ func (ccpack *CDSPackage) getCDSData(cds *pb.ChaincodeDeploymentSpec) ([]byte, * b, err := proto.Marshal(cds) if err != nil { - return nil, nil, err + return nil, nil, nil, err } if err = factory.InitFactories(nil); err != nil { - return nil, nil, fmt.Errorf("Internal error, BCCSP could not be initialized : %s", err) + return nil, nil, nil, fmt.Errorf("Internal error, BCCSP could not be initialized : %s", err) } //compute hashes now hash, err := factory.GetDefault().GetHash(&bccsp.SHAOpts{}) if err != nil { - return nil, nil, err + return nil, nil, nil, err } cdsdata := &CDSData{} @@ -116,46 +158,52 @@ func (ccpack *CDSPackage) getCDSData(cds *pb.ChaincodeDeploymentSpec) ([]byte, * b, err = proto.Marshal(cdsdata) if err != nil { - return nil, nil, err + return nil, nil, nil, err } - return b, cdsdata, nil + hash.Reset() + + //compute the id + hash.Write(cdsdata.CodeHash) + hash.Write(cdsdata.MetaDataHash) + + id := hash.Sum(nil) + + return b, id, cdsdata, nil } // ValidateCC returns error if the chaincode is not found or if its not a // ChaincodeDeploymentSpec -func (ccpack *CDSPackage) ValidateCC(ccdata *ChaincodeData) (*pb.ChaincodeDeploymentSpec, error) { +func (ccpack *CDSPackage) ValidateCC(ccdata *ChaincodeData) error { if ccpack.depSpec == nil { - return nil, fmt.Errorf("uninitialized package") + return fmt.Errorf("uninitialized package") } if ccpack.data == nil { - return nil, fmt.Errorf("nil data") + return fmt.Errorf("nil data") } if ccdata.Name != ccpack.depSpec.ChaincodeSpec.ChaincodeId.Name || ccdata.Version != ccpack.depSpec.ChaincodeSpec.ChaincodeId.Version { - return nil, fmt.Errorf("invalid chaincode data %v (%v)", ccdata, ccpack.depSpec.ChaincodeSpec.ChaincodeId) + return fmt.Errorf("invalid chaincode data %v (%v)", ccdata, ccpack.depSpec.ChaincodeSpec.ChaincodeId) } otherdata := &CDSData{} err := proto.Unmarshal(ccdata.Data, otherdata) if err != nil { - return nil, err + return err } if !ccpack.data.Equals(otherdata) { - return nil, fmt.Errorf("data mismatch") + return fmt.Errorf("data mismatch") } - return ccpack.depSpec, nil + return nil } //InitFromBuffer sets the buffer if valid and returns ChaincodeData func (ccpack *CDSPackage) InitFromBuffer(buf []byte) (*ChaincodeData, error) { //incase ccpack is reused - ccpack.buf = nil - ccpack.depSpec = nil - ccpack.data = nil + ccpack.reset() depSpec := &pb.ChaincodeDeploymentSpec{} err := proto.Unmarshal(buf, depSpec) @@ -163,7 +211,7 @@ func (ccpack *CDSPackage) InitFromBuffer(buf []byte) (*ChaincodeData, error) { return nil, fmt.Errorf("failed to unmarshal deployment spec from bytes") } - databytes, data, err := ccpack.getCDSData(depSpec) + databytes, id, data, err := ccpack.getCDSData(depSpec) if err != nil { return nil, err } @@ -171,38 +219,27 @@ func (ccpack *CDSPackage) InitFromBuffer(buf []byte) (*ChaincodeData, error) { ccpack.buf = buf ccpack.depSpec = depSpec ccpack.data = data + ccpack.datab = databytes + ccpack.id = id - return &ChaincodeData{Name: depSpec.ChaincodeSpec.ChaincodeId.Name, Version: depSpec.ChaincodeSpec.ChaincodeId.Version, Data: databytes}, nil + return ccpack.GetChaincodeData(), nil } //InitFromFS returns the chaincode and its package from the file system func (ccpack *CDSPackage) InitFromFS(ccname string, ccversion string) ([]byte, *pb.ChaincodeDeploymentSpec, error) { //incase ccpack is reused - ccpack.buf = nil - ccpack.depSpec = nil - ccpack.data = nil + ccpack.reset() buf, err := GetChaincodePackage(ccname, ccversion) if err != nil { return nil, nil, err } - depSpec := &pb.ChaincodeDeploymentSpec{} - err = proto.Unmarshal(buf, depSpec) - if err != nil { - return nil, nil, fmt.Errorf("failed to unmarshal fs deployment spec for %s, %s", ccname, ccversion) - } - - _, data, err := ccpack.getCDSData(depSpec) - if err != nil { + if _, err = ccpack.InitFromBuffer(buf); err != nil { return nil, nil, err } - ccpack.buf = buf - ccpack.depSpec = depSpec - ccpack.data = data - - return buf, depSpec, nil + return ccpack.buf, ccpack.depSpec, nil } //PutChaincodeToFS - serializes chaincode to a package on the file system @@ -211,6 +248,10 @@ func (ccpack *CDSPackage) PutChaincodeToFS() error { return fmt.Errorf("uninitialized package") } + if ccpack.id == nil { + return fmt.Errorf("id cannot be nil if buf is not nil") + } + if ccpack.depSpec == nil { return fmt.Errorf("depspec cannot be nil if buf is not nil") } @@ -219,6 +260,10 @@ func (ccpack *CDSPackage) PutChaincodeToFS() error { return fmt.Errorf("nil data") } + if ccpack.datab == nil { + return fmt.Errorf("nil data bytes") + } + ccname := ccpack.depSpec.ChaincodeSpec.ChaincodeId.Name ccversion := ccpack.depSpec.ChaincodeSpec.ChaincodeId.Version diff --git a/core/common/ccprovider/cdspackage_test.go b/core/common/ccprovider/cdspackage_test.go index a8cf21a4966..1d4772d3fbe 100644 --- a/core/common/ccprovider/cdspackage_test.go +++ b/core/common/ccprovider/cdspackage_test.go @@ -65,7 +65,7 @@ func TestPutCDSCC(t *testing.T) { return } - if _, err = ccpack.ValidateCC(cd); err != nil { + if err = ccpack.ValidateCC(cd); err != nil { t.Fatalf("error validating package %s", err) return } @@ -84,7 +84,7 @@ func TestPutCDSErrorPaths(t *testing.T) { } //validate with invalid name - if _, err = ccpack.ValidateCC(&ChaincodeData{Name: "invalname", Version: "0"}); err == nil { + if err = ccpack.ValidateCC(&ChaincodeData{Name: "invalname", Version: "0"}); err == nil { t.Fatalf("expected error validating package") return } @@ -169,7 +169,7 @@ func TestCDSSwitchChaincodes(t *testing.T) { return } - if _, err = badccpack.ValidateCC(goodcd); err == nil { + if err = badccpack.ValidateCC(goodcd); err == nil { t.Fatalf("expected goodcd to fail against bad package but succeeded!") return } diff --git a/core/common/ccprovider/sigcdspackage.go b/core/common/ccprovider/sigcdspackage.go index 363a8f5fec3..e163bfa65a0 100644 --- a/core/common/ccprovider/sigcdspackage.go +++ b/core/common/ccprovider/sigcdspackage.go @@ -71,19 +71,61 @@ type SignedCDSPackage struct { sDepSpec *pb.SignedChaincodeDeploymentSpec env *common.Envelope data *SignedCDSData + datab []byte + id []byte +} + +// resets data +func (ccpack *SignedCDSPackage) reset() { + *ccpack = SignedCDSPackage{} +} + +// GetId gets the fingerprint of the chaincode based on package computation +func (ccpack *SignedCDSPackage) GetId() []byte { + //this has to be after creating a package and initializing it + //If those steps fail, GetId() should never be called + if ccpack.id == nil { + panic("GetId called on uninitialized package") + } + return ccpack.id } // GetDepSpec gets the ChaincodeDeploymentSpec from the package func (ccpack *SignedCDSPackage) GetDepSpec() *pb.ChaincodeDeploymentSpec { + //this has to be after creating a package and initializing it + //If those steps fail, GetDepSpec() should never be called + if ccpack.depSpec == nil { + panic("GetDepSpec called on uninitialized package") + } return ccpack.depSpec } +// GetDepSpecBytes gets the serialized ChaincodeDeploymentSpec from the package +func (ccpack *SignedCDSPackage) GetDepSpecBytes() []byte { + //this has to be after creating a package and initializing it + //If those steps fail, GetDepSpecBytes() should never be called + if ccpack.sDepSpec == nil || ccpack.sDepSpec.ChaincodeDeploymentSpec == nil { + panic("GetDepSpecBytes called on uninitialized package") + } + return ccpack.sDepSpec.ChaincodeDeploymentSpec +} + // GetPackageObject gets the ChaincodeDeploymentSpec as proto.Message func (ccpack *SignedCDSPackage) GetPackageObject() proto.Message { return ccpack.env } -func (ccpack *SignedCDSPackage) getCDSData(scds *pb.SignedChaincodeDeploymentSpec) ([]byte, *SignedCDSData, error) { +// GetChaincodeData gets the ChaincodeData +func (ccpack *SignedCDSPackage) GetChaincodeData() *ChaincodeData { + //this has to be after creating a package and initializing it + //If those steps fail, GetChaincodeData() should never be called + if ccpack.depSpec == nil || ccpack.datab == nil || ccpack.id == nil { + panic("GetChaincodeData called on uninitialized package") + } + return &ChaincodeData{Name: ccpack.depSpec.ChaincodeSpec.ChaincodeId.Name, Version: ccpack.depSpec.ChaincodeSpec.ChaincodeId.Version, Data: ccpack.datab, Id: ccpack.id} +} + +func (ccpack *SignedCDSPackage) getCDSData(scds *pb.SignedChaincodeDeploymentSpec) ([]byte, []byte, *SignedCDSData, error) { // check for nil argument. It is an assertion that getCDSData // is never called on a package that did not go through/succeed // package initialization. @@ -94,17 +136,17 @@ func (ccpack *SignedCDSPackage) getCDSData(scds *pb.SignedChaincodeDeploymentSpe cds := &pb.ChaincodeDeploymentSpec{} err := proto.Unmarshal(scds.ChaincodeDeploymentSpec, cds) if err != nil { - return nil, nil, err + return nil, nil, nil, err } if err = factory.InitFactories(nil); err != nil { - return nil, nil, fmt.Errorf("Internal error, BCCSP could not be initialized : %s", err) + return nil, nil, nil, fmt.Errorf("Internal error, BCCSP could not be initialized : %s", err) } //get the hash object hash, err := factory.GetDefault().GetHash(&bccsp.SHAOpts{}) if err != nil { - return nil, nil, err + return nil, nil, nil, err } scdsdata := &SignedCDSData{} @@ -124,7 +166,7 @@ func (ccpack *SignedCDSPackage) getCDSData(scds *pb.SignedChaincodeDeploymentSpe //get the signature hashes if scds.InstantiationPolicy == nil { - return nil, nil, fmt.Errorf("instantiation policy cannot be nil for chaincode (%s:%s)", cds.ChaincodeSpec.ChaincodeId.Name, cds.ChaincodeSpec.ChaincodeId.Version) + return nil, nil, nil, fmt.Errorf("instantiation policy cannot be nil for chaincode (%s:%s)", cds.ChaincodeSpec.ChaincodeId.Name, cds.ChaincodeSpec.ChaincodeId.Version) } hash.Write(scds.InstantiationPolicy) @@ -136,52 +178,57 @@ func (ccpack *SignedCDSPackage) getCDSData(scds *pb.SignedChaincodeDeploymentSpe //marshall data b, err := proto.Marshal(scdsdata) if err != nil { - return nil, nil, err + return nil, nil, nil, err } - return b, scdsdata, nil + hash.Reset() + + //compute the id + hash.Write(scdsdata.CodeHash) + hash.Write(scdsdata.MetaDataHash) + hash.Write(scdsdata.SignatureHash) + + id := hash.Sum(nil) + + return b, id, scdsdata, nil } // ValidateCC returns error if the chaincode is not found or if its not a // ChaincodeDeploymentSpec -func (ccpack *SignedCDSPackage) ValidateCC(ccdata *ChaincodeData) (*pb.ChaincodeDeploymentSpec, error) { +func (ccpack *SignedCDSPackage) ValidateCC(ccdata *ChaincodeData) error { if ccpack.sDepSpec == nil { - return nil, fmt.Errorf("uninitialized package") + return fmt.Errorf("uninitialized package") } if ccpack.sDepSpec.ChaincodeDeploymentSpec == nil { - return nil, fmt.Errorf("signed chaincode deployment spec cannot be nil in a package") + return fmt.Errorf("signed chaincode deployment spec cannot be nil in a package") } if ccpack.depSpec == nil { - return nil, fmt.Errorf("chaincode deployment spec cannot be nil in a package") + return fmt.Errorf("chaincode deployment spec cannot be nil in a package") } if ccdata.Name != ccpack.depSpec.ChaincodeSpec.ChaincodeId.Name || ccdata.Version != ccpack.depSpec.ChaincodeSpec.ChaincodeId.Version { - return nil, fmt.Errorf("invalid chaincode data %v (%v)", ccdata, ccpack.depSpec.ChaincodeSpec.ChaincodeId) + return fmt.Errorf("invalid chaincode data %v (%v)", ccdata, ccpack.depSpec.ChaincodeSpec.ChaincodeId) } otherdata := &SignedCDSData{} err := proto.Unmarshal(ccdata.Data, otherdata) if err != nil { - return nil, err + return err } if !ccpack.data.Equals(otherdata) { - return nil, fmt.Errorf("data mismatch") + return fmt.Errorf("data mismatch") } - return ccpack.depSpec, nil + return nil } //InitFromBuffer sets the buffer if valid and returns ChaincodeData func (ccpack *SignedCDSPackage) InitFromBuffer(buf []byte) (*ChaincodeData, error) { //incase ccpack is reused - ccpack.buf = nil - ccpack.sDepSpec = nil - ccpack.depSpec = nil - ccpack.env = nil - ccpack.data = nil + ccpack.reset() env := &common.Envelope{} err := proto.Unmarshal(buf, env) @@ -202,7 +249,7 @@ func (ccpack *SignedCDSPackage) InitFromBuffer(buf []byte) (*ChaincodeData, erro return nil, fmt.Errorf("error getting deployment spec") } - databytes, data, err := ccpack.getCDSData(sDepSpec) + databytes, id, data, err := ccpack.getCDSData(sDepSpec) if err != nil { return nil, err } @@ -212,17 +259,16 @@ func (ccpack *SignedCDSPackage) InitFromBuffer(buf []byte) (*ChaincodeData, erro ccpack.depSpec = depSpec ccpack.env = env ccpack.data = data + ccpack.datab = databytes + ccpack.id = id - return &ChaincodeData{Name: depSpec.ChaincodeSpec.ChaincodeId.Name, Version: depSpec.ChaincodeSpec.ChaincodeId.Version, Data: databytes}, nil + return ccpack.GetChaincodeData(), nil } //InitFromFS returns the chaincode and its package from the file system func (ccpack *SignedCDSPackage) InitFromFS(ccname string, ccversion string) ([]byte, *pb.ChaincodeDeploymentSpec, error) { //incase ccpack is reused - ccpack.buf = nil - ccpack.sDepSpec = nil - ccpack.depSpec = nil - ccpack.env = nil + ccpack.reset() buf, err := GetChaincodePackage(ccname, ccversion) if err != nil { @@ -242,6 +288,10 @@ func (ccpack *SignedCDSPackage) PutChaincodeToFS() error { return fmt.Errorf("uninitialized package") } + if ccpack.id == nil { + return fmt.Errorf("id cannot be nil if buf is not nil") + } + if ccpack.sDepSpec == nil || ccpack.depSpec == nil { return fmt.Errorf("depspec cannot be nil if buf is not nil") } @@ -254,6 +304,10 @@ func (ccpack *SignedCDSPackage) PutChaincodeToFS() error { return fmt.Errorf("nil data") } + if ccpack.datab == nil { + return fmt.Errorf("nil data bytes") + } + ccname := ccpack.depSpec.ChaincodeSpec.ChaincodeId.Name ccversion := ccpack.depSpec.ChaincodeSpec.ChaincodeId.Version diff --git a/core/common/ccprovider/sigcdspackage_test.go b/core/common/ccprovider/sigcdspackage_test.go index 00ec5ce425f..c332b61e11d 100644 --- a/core/common/ccprovider/sigcdspackage_test.go +++ b/core/common/ccprovider/sigcdspackage_test.go @@ -62,7 +62,7 @@ func TestPutSigCDSCC(t *testing.T) { return } - if _, err = ccpack.ValidateCC(cd); err != nil { + if err = ccpack.ValidateCC(cd); err != nil { t.Fatalf("error validating package %s", err) return } @@ -81,7 +81,7 @@ func TestPutSignedCDSErrorPaths(t *testing.T) { } //validate with invalid name - if _, err = ccpack.ValidateCC(&ChaincodeData{Name: "invalname", Version: "0"}); err == nil { + if err = ccpack.ValidateCC(&ChaincodeData{Name: "invalname", Version: "0"}); err == nil { t.Fatalf("expected error validating package") return } @@ -197,7 +197,7 @@ func TestSignedCDSSwitchChaincodes(t *testing.T) { return } - if _, err = badccpack.ValidateCC(goodcd); err == nil { + if err = badccpack.ValidateCC(goodcd); err == nil { t.Fatalf("expected goodcd to fail against bad package but succeeded!") return } diff --git a/core/scc/lccc/lccc.go b/core/scc/lccc/lccc.go index f11082c6149..57deead83e3 100644 --- a/core/scc/lccc/lccc.go +++ b/core/scc/lccc/lccc.go @@ -180,6 +180,13 @@ func (f InvalidVersionErr) Error() string { return fmt.Sprintf("invalid chaincode version %s", string(f)) } +//ChaincodeMismatchErr chaincode name from two places don't match +type ChaincodeMismatchErr string + +func (f ChaincodeMismatchErr) Error() string { + return fmt.Sprintf("chaincode name mismatch %s", string(f)) +} + //EmptyVersionErr empty version error type EmptyVersionErr string @@ -201,69 +208,101 @@ func (f IdenticalVersionErr) Error() string { return fmt.Sprintf("chaincode with the same version exists %s", string(f)) } +//InvalidCCOnFSError error due to mismatch between fingerprint on lccc and installed CC +type InvalidCCOnFSError string + +func (f InvalidCCOnFSError) Error() string { + return fmt.Sprintf("chaincode fingerprint mismatch %s", string(f)) +} + //-------------- helper functions ------------------ //create the chaincode on the given chain -func (lccc *LifeCycleSysCC) createChaincode(stub shim.ChaincodeStubInterface, chainname string, ccname string, version string, policy []byte, escc []byte, vscc []byte) (*ccprovider.ChaincodeData, error) { - return lccc.putChaincodeData(stub, chainname, ccname, version, policy, escc, vscc) +func (lccc *LifeCycleSysCC) createChaincode(stub shim.ChaincodeStubInterface, cd *ccprovider.ChaincodeData) error { + return lccc.putChaincodeData(stub, cd) } //upgrade the chaincode on the given chain -func (lccc *LifeCycleSysCC) upgradeChaincode(stub shim.ChaincodeStubInterface, chainname string, ccname string, version string, policy []byte, escc []byte, vscc []byte) (*ccprovider.ChaincodeData, error) { - return lccc.putChaincodeData(stub, chainname, ccname, version, policy, escc, vscc) +func (lccc *LifeCycleSysCC) upgradeChaincode(stub shim.ChaincodeStubInterface, cd *ccprovider.ChaincodeData) error { + return lccc.putChaincodeData(stub, cd) } //create the chaincode on the given chain -func (lccc *LifeCycleSysCC) putChaincodeData(stub shim.ChaincodeStubInterface, chainname string, ccname string, version string, policy []byte, escc []byte, vscc []byte) (*ccprovider.ChaincodeData, error) { +func (lccc *LifeCycleSysCC) putChaincodeData(stub shim.ChaincodeStubInterface, cd *ccprovider.ChaincodeData) error { // check that escc and vscc are real system chaincodes - if !lccc.sccprovider.IsSysCC(string(escc)) { - return nil, fmt.Errorf("%s is not a valid endorsement system chaincode", string(escc)) + if !lccc.sccprovider.IsSysCC(string(cd.Escc)) { + return fmt.Errorf("%s is not a valid endorsement system chaincode", string(cd.Escc)) } - if !lccc.sccprovider.IsSysCC(string(vscc)) { - return nil, fmt.Errorf("%s is not a valid validation system chaincode", string(vscc)) + if !lccc.sccprovider.IsSysCC(string(cd.Vscc)) { + return fmt.Errorf("%s is not a valid validation system chaincode", string(cd.Vscc)) } - cd := &ccprovider.ChaincodeData{Name: ccname, Version: version, Policy: policy, Escc: string(escc), Vscc: string(vscc)} cdbytes, err := proto.Marshal(cd) if err != nil { - return nil, err + return err } if cdbytes == nil { - return nil, MarshallErr(ccname) + return MarshallErr(cd.Name) } - err = stub.PutState(ccname, cdbytes) + err = stub.PutState(cd.Name, cdbytes) - return cd, err + return err } -//checks for existence of chaincode on the given chain -func (lccc *LifeCycleSysCC) getChaincode(stub shim.ChaincodeStubInterface, ccname string, checkFS bool) (*ccprovider.ChaincodeData, []byte, *pb.ChaincodeDeploymentSpec, []byte, error) { +//checks for existence of chaincode on the given channel +func (lccc *LifeCycleSysCC) getCCInstance(stub shim.ChaincodeStubInterface, ccname string) ([]byte, error) { cdbytes, err := stub.GetState(ccname) if err != nil { - return nil, nil, nil, nil, err + return nil, TXNotFoundErr(err.Error()) + } + if cdbytes == nil { + return nil, NotFoundErr(ccname) } - if cdbytes != nil { - cd := &ccprovider.ChaincodeData{} - err = proto.Unmarshal(cdbytes, cd) - if err != nil { - return nil, nil, nil, nil, MarshallErr(ccname) - } + return cdbytes, nil +} - if checkFS { - depspecbytes, depspec, err := ccprovider.GetChaincodeFromFS(ccname, cd.Version) - if err != nil { - return cd, nil, nil, nil, InvalidDeploymentSpecErr(err.Error()) - } +//gets the cd out of the bytes +func (lccc *LifeCycleSysCC) getChaincodeData(ccname string, cdbytes []byte) (*ccprovider.ChaincodeData, error) { + cd := &ccprovider.ChaincodeData{} + err := proto.Unmarshal(cdbytes, cd) + if err != nil { + return nil, MarshallErr(ccname) + } - return cd, cdbytes, depspec, depspecbytes, nil - } + //this should not happen but still a sanity check is not a bad thing + if cd.Name != ccname { + return nil, ChaincodeMismatchErr(fmt.Sprintf("%s!=%s", ccname, cd.Name)) + } - return cd, cdbytes, nil, nil, nil + return cd, nil +} + +//checks for existence of chaincode on the given chain +func (lccc *LifeCycleSysCC) getCCCode(ccname string, cdbytes []byte) (*ccprovider.ChaincodeData, *pb.ChaincodeDeploymentSpec, []byte, error) { + cd, err := lccc.getChaincodeData(ccname, cdbytes) + if err != nil { + return nil, nil, nil, err } - return nil, nil, nil, nil, NotFoundErr(ccname) + ccpack, err := ccprovider.GetChaincodeFromFS(ccname, cd.Version) + if err != nil { + return nil, nil, nil, InvalidDeploymentSpecErr(err.Error()) + } + + //this is the big test and the reason every launch should go through + //getChaincode call. We validate the chaincode entry against the + //the chaincode in FS + if err = ccpack.ValidateCC(cd); err != nil { + return nil, nil, nil, InvalidCCOnFSError(err.Error()) + } + + //these are guaranteed to be non-nil because we got a valid ccpack + depspec := ccpack.GetDepSpec() + depspecbytes := ccpack.GetDepSpecBytes() + + return cd, depspec, depspecbytes, nil } // getChaincodes returns all chaincodes instantiated on this LCCC's channel @@ -295,10 +334,10 @@ func (lccc *LifeCycleSysCC) getChaincodes(stub shim.ChaincodeStubInterface) pb.R //if chaincode is not installed on the system we won't have //data beyond name and version - _, ccdepspec, err := ccprovider.GetChaincodeFromFS(ccdata.Name, ccdata.Version) + ccpack, err := ccprovider.GetChaincodeFromFS(ccdata.Name, ccdata.Version) if err == nil { - path = ccdepspec.GetChaincodeSpec().ChaincodeId.Path - input = ccdepspec.GetChaincodeSpec().Input.String() + path = ccpack.GetDepSpec().GetChaincodeSpec().ChaincodeId.Path + input = ccpack.GetDepSpec().GetChaincodeSpec().Input.String() } ccInfo := &pb.ChaincodeInfo{Name: ccdata.Name, Version: ccdata.Version, Path: path, Input: input, Escc: ccdata.Escc, Vscc: ccdata.Vscc} @@ -438,12 +477,27 @@ func (lccc *LifeCycleSysCC) executeDeploy(stub shim.ChaincodeStubInterface, chai return nil, err } - cd, _, _, _, err := lccc.getChaincode(stub, cds.ChaincodeSpec.ChaincodeId.Name, true) - if cd != nil { + //just test for existence of the chaincode in the LCCC + _, err = lccc.getCCInstance(stub, cds.ChaincodeSpec.ChaincodeId.Name) + if err == nil { return nil, ExistsErr(cds.ChaincodeSpec.ChaincodeId.Name) } - cd, err = lccc.createChaincode(stub, chainname, cds.ChaincodeSpec.ChaincodeId.Name, cds.ChaincodeSpec.ChaincodeId.Version, policy, escc, vscc) + //get the chaincode from the FS + ccpack, err := ccprovider.GetChaincodeFromFS(cds.ChaincodeSpec.ChaincodeId.Name, cds.ChaincodeSpec.ChaincodeId.Version) + if err != nil { + return nil, fmt.Errorf("cannot get package for the chaincode to be instantiated (%s:%s)-%s", cds.ChaincodeSpec.ChaincodeId.Name, cds.ChaincodeSpec.ChaincodeId.Version, err) + } + + //this is guranteed to be not nil + cd := ccpack.GetChaincodeData() + + //retain chaincode specific data and fill channel specific ones + cd.Escc = string(escc) + cd.Vscc = string(vscc) + cd.Policy = policy + + err = lccc.createChaincode(stub, cd) return cd, err } @@ -468,24 +522,44 @@ func (lccc *LifeCycleSysCC) executeUpgrade(stub shim.ChaincodeStubInterface, cha return nil, err } - // check for existence of chaincode - cd, _, _, _, err := lccc.getChaincode(stub, chaincodeName, true) - if cd == nil { + // check for existence of chaincode instance only (it has to exist on the channel) + // we dont care about the old chaincode on the FS. In particular, user may even + // have deleted it + cdbytes, _ := lccc.getCCInstance(stub, chaincodeName) + if cdbytes == nil { return nil, NotFoundErr(chainName) } + //we need the cd to compare the version + cd, err := lccc.getChaincodeData(chaincodeName, cdbytes) + if err != nil { + return nil, err + } + + //do not upgrade if same version if cd.Version == cds.ChaincodeSpec.ChaincodeId.Version { return nil, IdenticalVersionErr(cds.ChaincodeSpec.ChaincodeId.Name) } - ver := cds.ChaincodeSpec.ChaincodeId.Version + ccpack, err := ccprovider.GetChaincodeFromFS(chaincodeName, cds.ChaincodeSpec.ChaincodeId.Version) + if err != nil { + return nil, fmt.Errorf("cannot get package for the chaincode to be upgraded (%s:%s)-%s", chaincodeName, cds.ChaincodeSpec.ChaincodeId.Version, err) + } - newCD, err := lccc.upgradeChaincode(stub, chainName, chaincodeName, ver, policy, escc, vscc) + //get the new cd to upgrade to this is guaranteed to be not nil + cd = ccpack.GetChaincodeData() + + //retain chaincode specific data and fill channel specific ones + cd.Escc = string(escc) + cd.Vscc = string(vscc) + cd.Policy = policy + + err = lccc.upgradeChaincode(stub, cd) if err != nil { return nil, err } - return newCD, nil + return cd, nil } //-------------- the chaincode stub interface implementation ---------- @@ -653,24 +727,26 @@ func (lccc *LifeCycleSysCC) Invoke(stub shim.ChaincodeStubInterface) pb.Response chain := string(args[1]) ccname := string(args[2]) - //check the FS only for deployment spec - //other calls are looking for LCCC entries only - checkFS := false - if function == GETDEPSPEC { - checkFS = true - } - cd, cdbytes, _, depspecbytes, err := lccc.getChaincode(stub, ccname, checkFS) - if cd == nil || cdbytes == nil { - logger.Errorf("ChaincodeId: %s does not exist on channel: %s(err:%s)", ccname, chain, err) - return shim.Error(TXNotFoundErr(ccname + "/" + chain).Error()) + cdbytes, err := lccc.getCCInstance(stub, ccname) + if err != nil { + logger.Errorf("error getting chaincode %s on channel: %s(err:%s)", ccname, chain, err) + return shim.Error(err.Error()) } switch function { case GETCCINFO: + cd, err := lccc.getChaincodeData(ccname, cdbytes) + if err != nil { + return shim.Error(err.Error()) + } return shim.Success([]byte(cd.Name)) case GETCCDATA: return shim.Success(cdbytes) default: + _, _, depspecbytes, err := lccc.getCCCode(ccname, cdbytes) + if err != nil { + return shim.Error(err.Error()) + } return shim.Success(depspecbytes) } case GETCHAINCODES: diff --git a/core/scc/lccc/lccc_test.go b/core/scc/lccc/lccc_test.go index 407bebb9485..259111b49d4 100644 --- a/core/scc/lccc/lccc_test.go +++ b/core/scc/lccc/lccc_test.go @@ -17,9 +17,10 @@ package lccc import ( "fmt" - "testing" - + "io/ioutil" "os" + "strings" + "testing" "github.com/golang/protobuf/proto" "github.com/hyperledger/fabric/core/chaincode/shim" @@ -454,6 +455,87 @@ func TestRetryFailedDeploy(t *testing.T) { } } +//TestTamperChaincode modifies the chaincode on the FS after deploy +func TestTamperChaincode(t *testing.T) { + scc := new(LifeCycleSysCC) + stub := shim.NewMockStub("lccc", scc) + + if res := stub.MockInit("1", nil); res.Status != shim.OK { + fmt.Println("Init failed", string(res.Message)) + t.FailNow() + } + + //deploy 01 + cds, err := constructDeploymentSpec("example01", "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example01", "0", [][]byte{[]byte("init"), []byte("a"), []byte("1"), []byte("b"), []byte("2")}, true) + if err != nil { + t.Logf("Could not construct example01.0") + t.FailNow() + } + + defer os.Remove(lccctestpath + "/example01.0") + + var b []byte + if b, err = proto.Marshal(cds); err != nil || b == nil { + t.Logf("Could not construct example01.0") + t.FailNow() + } + + args := [][]byte{[]byte(DEPLOY), []byte("test"), b} + res := stub.MockInvoke("1", args) + if res.Status != shim.OK { + t.Logf("Could not deploy example01.0") + t.FailNow() + } + + //deploy 02 + cds, err = constructDeploymentSpec("example02", "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02", "0", [][]byte{[]byte("init"), []byte("a"), []byte("100"), []byte("b"), []byte("200")}, true) + if err != nil { + t.FailNow() + } + + defer os.Remove(lccctestpath + "/example02.0") + + if b, err = proto.Marshal(cds); err != nil || b == nil { + t.FailNow() + } + + //deploy correctly now + args = [][]byte{[]byte(DEPLOY), []byte("test"), b} + if res = stub.MockInvoke("1", args); res.Status != shim.OK { + t.Logf("Could not deploy example02.0") + t.FailNow() + } + + //remove the old file... + os.Remove(lccctestpath + "/example02.0") + + //read 01 and ... + if b, err = ioutil.ReadFile(lccctestpath + "/example01.0"); err != nil { + t.Logf("Could not read back example01.0") + t.FailNow() + } + + //...brute force replace 02 with bytes from 01 + if err = ioutil.WriteFile(lccctestpath+"/example02.0", b, 0644); err != nil { + t.Logf("Could not write to example02.0") + t.FailNow() + } + + //get the deploymentspec + args = [][]byte{[]byte(GETDEPSPEC), []byte("test"), []byte(cds.ChaincodeSpec.ChaincodeId.Name)} + if res = stub.MockInvoke("1", args); res.Status == shim.OK { + t.Logf("Expected error on tampering files but succeeded") + t.FailNow() + } + + //look specifically for Invalid error + expectedErr := InvalidCCOnFSError("").Error() + if strings.Index(res.Message, expectedErr) < 0 { + t.Logf("Expected prefix %s on error but appeared to have got a different error : %+v", expectedErr, res) + t.FailNow() + } +} + // TestUpgrade tests the upgrade function with various inputs for basic use cases func TestUpgrade(t *testing.T) { path := "github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02"