/* Copyright IBM Corp. 2016-2019 All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ package ccpackage import ( "bytes" "errors" "fmt" "github.com/golang/protobuf/proto" "github.com/hyperledger/fabric-protos-go/common" "github.com/hyperledger/fabric-protos-go/peer" "github.com/hyperledger/fabric/internal/pkg/identity" "github.com/hyperledger/fabric/protoutil" ) // ExtractSignedCCDepSpec extracts the messages from the envelope func ExtractSignedCCDepSpec(env *common.Envelope) (*common.ChannelHeader, *peer.SignedChaincodeDeploymentSpec, error) { p := &common.Payload{} err := proto.Unmarshal(env.Payload, p) if err != nil { return nil, nil, err } if p.Header == nil { return nil, nil, errors.New("channel header cannot be nil") } ch := &common.ChannelHeader{} err = proto.Unmarshal(p.Header.ChannelHeader, ch) if err != nil { return nil, nil, err } sp := &peer.SignedChaincodeDeploymentSpec{} err = proto.Unmarshal(p.Data, sp) if err != nil { return nil, nil, err } return ch, sp, nil } // This file provides functions for helping with the chaincode install // package workflow. In particular // OwnerCreateSignedCCDepSpec - each owner creates signs the package using the same deploy // CreateSignedCCDepSpecForInstall - an admin or owner creates the package to be installed // using the packages from OwnerCreateSignedCCDepSpec // ValidateCip validate the endorsed package against the base package func ValidateCip(baseCip, otherCip *peer.SignedChaincodeDeploymentSpec) error { if baseCip == nil || otherCip == nil { panic("do not call with nil parameters") } if (baseCip.OwnerEndorsements == nil && otherCip.OwnerEndorsements != nil) || (baseCip.OwnerEndorsements != nil && otherCip.OwnerEndorsements == nil) { return fmt.Errorf("endorsements should either be both nil or not nil") } bN := len(baseCip.OwnerEndorsements) oN := len(otherCip.OwnerEndorsements) if bN > 1 || oN > 1 { return fmt.Errorf("expect utmost 1 endorsement from a owner") } if bN != oN { return fmt.Errorf("Rule-all packages should be endorsed or none should be endorsed failed for (%d, %d)", bN, oN) } if !bytes.Equal(baseCip.ChaincodeDeploymentSpec, otherCip.ChaincodeDeploymentSpec) { return fmt.Errorf("Rule-all deployment specs should match(%d, %d)", len(baseCip.ChaincodeDeploymentSpec), len(otherCip.ChaincodeDeploymentSpec)) } if !bytes.Equal(baseCip.InstantiationPolicy, otherCip.InstantiationPolicy) { return fmt.Errorf("Rule-all instantiation policies should match(%d, %d)", len(baseCip.InstantiationPolicy), len(otherCip.InstantiationPolicy)) } return nil } func createSignedCCDepSpec(cdsbytes []byte, instpolicybytes []byte, endorsements []*peer.Endorsement) (*common.Envelope, error) { if cdsbytes == nil { return nil, fmt.Errorf("nil chaincode deployment spec") } if instpolicybytes == nil { return nil, fmt.Errorf("nil instantiation policy") } // create SignedChaincodeDeploymentSpec... cip := &peer.SignedChaincodeDeploymentSpec{ChaincodeDeploymentSpec: cdsbytes, InstantiationPolicy: instpolicybytes, OwnerEndorsements: endorsements} //...and marshal it cipbytes := protoutil.MarshalOrPanic(cip) // use defaults (this is definitely ok for install package) msgVersion := int32(0) epoch := uint64(0) chdr := protoutil.MakeChannelHeader(common.HeaderType_CHAINCODE_PACKAGE, msgVersion, "", epoch) // create the payload payl := &common.Payload{Header: &common.Header{ChannelHeader: protoutil.MarshalOrPanic(chdr)}, Data: cipbytes} paylBytes, err := protoutil.GetBytesPayload(payl) if err != nil { return nil, err } // here's the unsigned envelope. The install package is endorsed if signingEntity != nil return &common.Envelope{Payload: paylBytes}, nil } // CreateSignedCCDepSpecForInstall creates the final package from a set of packages signed by // owners. This is similar to how the SDK assembles a TX from various proposal // responses from the signatures. func CreateSignedCCDepSpecForInstall(pack []*common.Envelope) (*common.Envelope, error) { if len(pack) == 0 { return nil, errors.New("no packages provided to collate") } // rules... // all packages must be endorsed or all packages should not be endorsed // the chaincode deployment spec should be same var baseCip *peer.SignedChaincodeDeploymentSpec var err error var endorsementExists bool var endorsements []*peer.Endorsement for n, r := range pack { p := &common.Payload{} if err = proto.Unmarshal(r.Payload, p); err != nil { return nil, err } cip := &peer.SignedChaincodeDeploymentSpec{} if err = proto.Unmarshal(p.Data, cip); err != nil { return nil, err } // if its the first element, check if it has endorsement so we can // enforce endorsement rules if n == 0 { baseCip = cip // if it has endorsement, all other owners should have signed too if len(cip.OwnerEndorsements) > 0 { endorsementExists = true endorsements = make([]*peer.Endorsement, len(pack)) } } else if err = ValidateCip(baseCip, cip); err != nil { return nil, err } if endorsementExists { endorsements[n] = cip.OwnerEndorsements[0] } } return createSignedCCDepSpec(baseCip.ChaincodeDeploymentSpec, baseCip.InstantiationPolicy, endorsements) } // OwnerCreateSignedCCDepSpec creates a package from a ChaincodeDeploymentSpec and // optionally endorses it func OwnerCreateSignedCCDepSpec(cds *peer.ChaincodeDeploymentSpec, instPolicy *common.SignaturePolicyEnvelope, owner identity.SignerSerializer) (*common.Envelope, error) { if cds == nil { return nil, fmt.Errorf("invalid chaincode deployment spec") } if instPolicy == nil { return nil, fmt.Errorf("must provide an instantiation policy") } cdsbytes := protoutil.MarshalOrPanic(cds) instpolicybytes := protoutil.MarshalOrPanic(instPolicy) var endorsements []*peer.Endorsement // it is not mandatory (at this protoutil level) to have a signature // this is especially convenient during dev/test // it may be necessary to enforce it via a policy at a higher level if owner != nil { // serialize the signing identity endorser, err := owner.Serialize() if err != nil { return nil, fmt.Errorf("Could not serialize the signing identity: %s", err) } // sign the concatenation of cds, instpolicy and the serialized endorser identity with this endorser's key signature, err := owner.Sign(append(cdsbytes, append(instpolicybytes, endorser...)...)) if err != nil { return nil, fmt.Errorf("Could not sign the ccpackage, err %s", err) } // each owner starts off the endorsements with one element. All such endorsed // packages will be collected in a final package by CreateSignedCCDepSpecForInstall // when endorsements will have all the entries endorsements = make([]*peer.Endorsement, 1) endorsements[0] = &peer.Endorsement{Signature: signature, Endorser: endorser} } return createSignedCCDepSpec(cdsbytes, instpolicybytes, endorsements) } // SignExistingPackage adds a signature to a signed package. func SignExistingPackage(env *common.Envelope, owner identity.SignerSerializer) (*common.Envelope, error) { if owner == nil { return nil, fmt.Errorf("owner not provided") } ch, sdepspec, err := ExtractSignedCCDepSpec(env) if err != nil { return nil, err } if ch == nil { return nil, fmt.Errorf("channel header not found in the envelope") } if sdepspec == nil || sdepspec.ChaincodeDeploymentSpec == nil || sdepspec.InstantiationPolicy == nil || sdepspec.OwnerEndorsements == nil { return nil, fmt.Errorf("invalid signed deployment spec") } // serialize the signing identity endorser, err := owner.Serialize() if err != nil { return nil, fmt.Errorf("Could not serialize the signing identity: %s", err) } // sign the concatenation of cds, instpolicy and the serialized endorser identity with this endorser's key signature, err := owner.Sign(append(sdepspec.ChaincodeDeploymentSpec, append(sdepspec.InstantiationPolicy, endorser...)...)) if err != nil { return nil, fmt.Errorf("Could not sign the ccpackage, err %s", err) } endorsements := append(sdepspec.OwnerEndorsements, &peer.Endorsement{Signature: signature, Endorser: endorser}) return createSignedCCDepSpec(sdepspec.ChaincodeDeploymentSpec, sdepspec.InstantiationPolicy, endorsements) }