Skip to content

Commit

Permalink
[FABG-723] Support distributed signing identities
Browse files Browse the repository at this point in the history
	Initial commit with new functionality and unit tests.
	Another commit will be pushed later to include integration tests

Change-Id: If34333e8cc2a6c267f579b956a03452e6fbd92a5
Signed-off-by: Baha Shaaban <[email protected]>
  • Loading branch information
Baha Shaaban committed Sep 5, 2018
1 parent 8113dec commit 6721a4c
Show file tree
Hide file tree
Showing 4 changed files with 480 additions and 34 deletions.
27 changes: 26 additions & 1 deletion pkg/client/resmgmt/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ package resmgmt

import (
reqContext "context"
"io"
"time"

"github.com/hyperledger/fabric-sdk-go/pkg/common/errors/retry"
"github.com/hyperledger/fabric-sdk-go/pkg/common/providers/context"
"github.com/hyperledger/fabric-sdk-go/pkg/common/providers/fab"
"github.com/hyperledger/fabric-sdk-go/pkg/fab/comm"
"github.com/hyperledger/fabric-sdk-go/third_party/github.com/hyperledger/fabric/protos/common"
"github.com/pkg/errors"
)

Expand Down Expand Up @@ -68,7 +70,7 @@ func WithTargetFilter(targetFilter fab.TargetFilter) RequestOption {
}
}

//WithTimeout encapsulates key value pairs of timeout type, timeout duration to Options
// WithTimeout encapsulates key value pairs of timeout type, timeout duration to Options
//if not provided, default timeout configuration from config will be used
func WithTimeout(timeoutType fab.TimeoutType, timeout time.Duration) RequestOption {
return func(ctx context.Client, o *requestOptions) error {
Expand Down Expand Up @@ -109,6 +111,29 @@ func WithOrderer(orderer fab.Orderer) RequestOption {
}
}

// WithConfigSignatures allows to provide pre defined signatures for resmgmt client's SaveChannel call
func WithConfigSignatures(signatures ...*common.ConfigSignature) RequestOption {
return func(ctx context.Client, opts *requestOptions) error {
opts.Signatures = signatures
return nil
}
}

// withConfigSignature allows to provide a pre defined signature reader for resmgmt client's SaveChannel call
// The r reader must provide marshaled ConfigSignature content built using either one of the following calls:
// * CreateConfigSignature call for a signature created internally by the SDK
// * CreateConfigSignatureData call with signingBytes used for creating a signature by external tool (ex: Openssl)
//
// Note: call this function for as many times as there are signatures required for the channel update.
// This option appends 1 ConfigSignature read from r to requestOptions.Signatures.
//
// Note : function not exported for now TODO: double check how to export this
func withConfigSignature(r io.Reader) RequestOption { // nolint
return func(ctx context.Client, opts *requestOptions) error {
return createConfigSignatureOption(r, opts)
}
}

//WithParentContext encapsulates grpc parent context.
func WithParentContext(parentContext reqContext.Context) RequestOption {
return func(ctx context.Client, o *requestOptions) error {
Expand Down
178 changes: 163 additions & 15 deletions pkg/client/resmgmt/resmgmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ import (
"os"
"time"

"github.com/hyperledger/fabric-sdk-go/pkg/common/errors/retry"

"github.com/golang/protobuf/proto"
"github.com/hyperledger/fabric-sdk-go/pkg/client/common/verifier"
"github.com/hyperledger/fabric-sdk-go/pkg/common/errors/retry"
"github.com/hyperledger/fabric-sdk-go/pkg/common/providers/fab"
"github.com/hyperledger/fabric-sdk-go/pkg/common/providers/msp"
"github.com/hyperledger/fabric-sdk-go/pkg/fab/channel"
Expand All @@ -48,6 +48,8 @@ import (
"github.com/pkg/errors"
)

const bufferSize = 1024

// InstallCCRequest contains install chaincode request parameters
type InstallCCRequest struct {
Name string
Expand Down Expand Up @@ -101,6 +103,8 @@ type requestOptions struct {
Timeouts map[fab.TimeoutType]time.Duration //timeout options for resmgmt operations
ParentContext reqContext.Context //parent grpc context for resmgmt operations
Retry retry.Opts
// signatures for channel configurations, if set, this option will take precedence over signatures of SaveChannelRequest.SigningIdentities
Signatures []*common.ConfigSignature
}

//SaveChannelRequest holds parameters for save channel request
Expand All @@ -109,7 +113,6 @@ type SaveChannelRequest struct {
ChannelConfig io.Reader // ChannelConfig data source
ChannelConfigPath string // Convenience option to use the named file as ChannelConfig reader
SigningIdentities []msp.SigningIdentity // Users that sign channel configuration
// TODO: support pre-signed signature blocks
}

// SaveChannelResponse contains response parameters for save channel
Expand Down Expand Up @@ -832,6 +835,9 @@ func peersToTxnProcessors(peers []fab.Peer) []fab.ProposalProcessor {
// Parameters:
// req holds info about mandatory channel name and configuration
// options holds optional request options
// if options have signatures (WithConfigSignatures() or 1 or more WithConfigSignature() calls), then SaveChannel will
// use these signatures instead of creating ones for the SigningIdentities found in req.
// Make sure that req.ChannelConfigPath/req.ChannelConfig have the channel config matching these signatures.
//
// Returns:
// save channel response with transaction ID
Expand All @@ -858,24 +864,24 @@ func (rc *Client) SaveChannel(req SaveChannelRequest, options ...RequestOption)

logger.Debugf("saving channel: %s", req.ChannelID)

configTx, err := ioutil.ReadAll(req.ChannelConfig)
chConfig, err := extractChConfigData(req.ChannelConfig)
if err != nil {
return SaveChannelResponse{}, errors.WithMessage(err, "reading channel config file failed")
}

chConfig, err := resource.ExtractChannelConfig(configTx)
if err != nil {
return SaveChannelResponse{}, errors.WithMessage(err, "extracting channel config failed")
return SaveChannelResponse{}, errors.WithMessage(err, "extracting channel config from ConfigTx failed")
}

orderer, err := rc.requestOrderer(&opts, req.ChannelID)
if err != nil {
return SaveChannelResponse{}, errors.WithMessage(err, "failed to find orderer for request")
}

configSignatures, err := rc.getConfigSignatures(req, chConfig)
if err != nil {
return SaveChannelResponse{}, err
var configSignatures []*common.ConfigSignature
if opts.Signatures != nil {
configSignatures = opts.Signatures
} else {
configSignatures, err = rc.getConfigSignatures(req, chConfig)
if err != nil {
return SaveChannelResponse{}, err
}
}

request := resource.CreateChannelRequest{
Expand Down Expand Up @@ -905,7 +911,6 @@ func (rc *Client) validateSaveChannelRequest(req SaveChannelRequest) error {
}

func (rc *Client) getConfigSignatures(req SaveChannelRequest, chConfig []byte) ([]*common.ConfigSignature, error) {

// Signing user has to belong to one of configured channel organisations
// In case that order org is one of channel orgs we can use context user
var signers []msp.SigningIdentity
Expand All @@ -922,6 +927,150 @@ func (rc *Client) getConfigSignatures(req SaveChannelRequest, chConfig []byte) (
return nil, errors.New("must provide signing user")
}

return rc.createCfgSigFromIDs(chConfig, signers...)
}

func readChConfigData(channelConfigPath string) ([]byte, error) {
if channelConfigPath == "" {
return nil, errors.New("must provide a channel config path")
}

configReader, err := os.Open(channelConfigPath) // nolint
if err != nil {
return nil, errors.Wrapf(err, "opening channel config file failed")
}
defer loggedClose(configReader)

chConfig, err := extractChConfigData(configReader)
if err != nil {
return nil, errors.WithMessage(err, "extracting channel config from channel config reader of channelConfigPath failed")
}
return chConfig, nil
}

func extractChConfigData(channelConfigReader io.Reader) ([]byte, error) {
if channelConfigReader == nil {
return nil, errors.New("must provide a non empty channel config file")
}
configTx, err := ioutil.ReadAll(channelConfigReader)
if err != nil {
return nil, errors.WithMessage(err, "reading channel config file failed")
}

chConfig, err := resource.ExtractChannelConfig(configTx)
if err != nil {
return nil, errors.WithMessage(err, "extracting channel config from ConfigTx failed")
}

return chConfig, nil
}

// CreateConfigSignature creates a signature for the given client, custom signers and chConfig from channelConfigPath argument
// return ConfigSignature will be signed internally by the SDK. It can be passed to WithConfigSignatures() option
func (rc *Client) CreateConfigSignature(signer msp.SigningIdentity, channelConfigPath string) (*common.ConfigSignature, error) {
chConfig, err := readChConfigData(channelConfigPath)
if err != nil {
return nil, err
}

sigs, err := rc.createCfgSigFromIDs(chConfig, signer)
if err != nil {
return nil, err
}

if len(sigs) != 1 {
return nil, errors.New("creating a config signature for 1 identity did not return 1 signature")
}

return sigs[0], nil
}

// CreateConfigSignatureData will prepare a SignatureHeader and the full signing []byte (signingBytes) to be used for signing a Channel Config
// Once SigningBytes have been signed externally (signing signatureHeaderData.SigningBytes using an external tool like OpenSSL), do the following:
// 1. create a common.ConfigSignature{} instance
// 2. assign its SignatureHeader field with the returned field 'signatureHeaderData.signatureHeader'
// 3. assign its Signature field with the generated signature of 'signatureHeaderData.signingBytes' from the external tool
// Then use WithConfigSignatures() option to pass this new instance for channel updates
func (rc *Client) CreateConfigSignatureData(signer msp.SigningIdentity, channelConfigPath string) (signatureHeaderData resource.ConfigSignatureData, e error) {
chConfig, err := readChConfigData(channelConfigPath)
if err != nil {
e = err
return
}
sigCtx := contextImpl.Client{
SigningIdentity: signer,
Providers: rc.ctx,
}

return resource.GetConfigSignatureData(&sigCtx, chConfig)
}

// MarshalConfigSignature marshals 1 ConfigSignature for the given client concatenated as []byte
func MarshalConfigSignature(signature *common.ConfigSignature) ([]byte, error) {
mSig, err := proto.Marshal(signature)
if err != nil {
return nil, errors.WithMessage(err, "failed to marshal signature")
}
return mSig, nil
}

// UnmarshalConfigSignature reads 1 ConfigSignature as []byte from reader and unmarshals it
func UnmarshalConfigSignature(reader io.Reader) (*common.ConfigSignature, error) {
arr, err := readConfigSignatureArray(reader)
if err != nil {
return nil, errors.Wrap(err, "reading ConfigSiganture array failed")
}

configSignature := &common.ConfigSignature{}
err = proto.Unmarshal(arr, configSignature)
if err != nil {
return nil, errors.WithMessage(err, "Failed to unmarshal config signature")
}
return configSignature, nil
}

func createConfigSignatureOption(r io.Reader, opts *requestOptions) error {
arr, err := readConfigSignatureArray(r)
if err != nil {
logger.Warnf("Failed to read channel config signature from bytes array: %s .. ignoring", err)
return err
}

singleSig := &common.ConfigSignature{}

err = proto.Unmarshal(arr, singleSig)
if err != nil {
logger.Warnf("Failed to unmarshal channel config signature from bytes array: %s .. ignoring signature", err)
return err
}

opts.Signatures = append(opts.Signatures, singleSig)

return nil
}

func readConfigSignatureArray(reader io.Reader) ([]byte, error) {
buff := make([]byte, bufferSize)
arr := []byte{}
for {
n, err := reader.Read(buff)
if err != nil && err != io.EOF {
logger.Warnf("Failed to read config signature data from reader: %s", err)
return nil, errors.WithMessage(err, "Failed to read config signature data from reader")
}

if n == 0 {
break
} else if n < bufferSize {
arr = append(arr, buff[:n]...)
} else {
arr = append(arr, buff...)
}
}
return arr, nil
}

func (rc *Client) createCfgSigFromIDs(chConfig []byte, signers ...msp.SigningIdentity) ([]*common.ConfigSignature, error) {
var configSignatures []*common.ConfigSignature
for _, signer := range signers {

Expand All @@ -938,7 +1087,6 @@ func (rc *Client) getConfigSignatures(req SaveChannelRequest, chConfig []byte) (
}

return configSignatures, nil

}

func loggedClose(c io.Closer) {
Expand Down
Loading

0 comments on commit 6721a4c

Please sign in to comment.