From e507869f957d9ec6d20998ca438a6b727308f4a2 Mon Sep 17 00:00:00 2001 From: Will Lahti Date: Wed, 15 Apr 2020 10:56:52 -0400 Subject: [PATCH] Extend SigningIdentity with config update signing functions (#1089) FAB-17741 Signed-off-by: Will Lahti --- integration/config/config_test.go | 4 +- pkg/config/config.go | 162 +----------------------------- pkg/config/config_test.go | 12 +-- pkg/config/example_test.go | 4 +- pkg/config/signer.go | 148 +++++++++++++++++++++++++++ 5 files changed, 159 insertions(+), 171 deletions(-) diff --git a/integration/config/config_test.go b/integration/config/config_test.go index d11909265da..74bb886f31a 100644 --- a/integration/config/config_test.go +++ b/integration/config/config_test.go @@ -162,11 +162,11 @@ var _ = Describe("Config", func() { PrivateKey: parsePeerPrivateKey(network, peer, "Admin"), MSPID: network.Organization(peer.Organization).MSPID, } - signature, err := config.SignConfigUpdate(configUpdate, signingIdentity) + signature, err := signingIdentity.SignConfigUpdate(configUpdate) Expect(err).NotTo(HaveOccurred()) By("creating a signed config update envelope with the detached signature") - configUpdateEnvelope, err := config.CreateSignedConfigUpdateEnvelope(configUpdate, signingIdentity, signature) + configUpdateEnvelope, err := signingIdentity.SignConfigUpdateEnvelope(configUpdate, signature) Expect(err).NotTo(HaveOccurred()) configUpdateBytes, err := proto.Marshal(configUpdateEnvelope) Expect(err).NotTo(HaveOccurred()) diff --git a/pkg/config/config.go b/pkg/config/config.go index c2c239f89b2..f0222a724a2 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -8,14 +8,10 @@ SPDX-License-Identifier: Apache-2.0 package config import ( - "bytes" - "crypto/rand" - "encoding/pem" "errors" "fmt" "strconv" "strings" - "time" "github.com/golang/protobuf/proto" "github.com/golang/protobuf/ptypes" @@ -210,98 +206,6 @@ func NewCreateChannelTx(channelConfig Channel, channelID string) (*cb.Envelope, return env, nil } -// SignConfigUpdate signs the given configuration update with a -// specified signing identity and returns a config signature. -func SignConfigUpdate(configUpdate *cb.ConfigUpdate, signingIdentity SigningIdentity) (*cb.ConfigSignature, error) { - signatureHeader, err := signatureHeader(signingIdentity) - if err != nil { - return nil, fmt.Errorf("failed to create signature header: %v", err) - } - - header, err := proto.Marshal(signatureHeader) - if err != nil { - return nil, fmt.Errorf("marshaling signature header: %v", err) - } - - configSignature := &cb.ConfigSignature{ - SignatureHeader: header, - } - - configUpdateBytes, err := proto.Marshal(configUpdate) - if err != nil { - return nil, fmt.Errorf("marshaling config update: %v", err) - } - - configSignature.Signature, err = signingIdentity.Sign( - rand.Reader, - concatenateBytes(configSignature.SignatureHeader, configUpdateBytes), - nil, - ) - if err != nil { - return nil, fmt.Errorf("failed to sign config update: %v", err) - } - - return configSignature, nil -} - -// CreateSignedConfigUpdateEnvelope creates a signed configuration update envelope. -func CreateSignedConfigUpdateEnvelope(configUpdate *cb.ConfigUpdate, signingIdentity SigningIdentity, - signatures ...*cb.ConfigSignature) (*cb.Envelope, error) { - update, err := proto.Marshal(configUpdate) - if err != nil { - return nil, fmt.Errorf("marshaling config update: %v", err) - } - - configUpdateEnvelope := &cb.ConfigUpdateEnvelope{ - ConfigUpdate: update, - Signatures: signatures, - } - - signedEnvelope, err := createSignedEnvelopeWithTLSBinding(cb.HeaderType_CONFIG_UPDATE, configUpdate.ChannelId, - signingIdentity, configUpdateEnvelope) - if err != nil { - return nil, fmt.Errorf("failed to create signed config update envelope: %v", err) - } - - return signedEnvelope, nil -} - -func signatureHeader(signingIdentity SigningIdentity) (*cb.SignatureHeader, error) { - buffer := bytes.NewBuffer(nil) - - err := pem.Encode(buffer, &pem.Block{Type: "CERTIFICATE", Bytes: signingIdentity.Certificate.Raw}) - if err != nil { - return nil, fmt.Errorf("pem encode: %v", err) - } - - idBytes, err := proto.Marshal(&mb.SerializedIdentity{Mspid: signingIdentity.MSPID, IdBytes: buffer.Bytes()}) - if err != nil { - return nil, fmt.Errorf("marshaling serialized identity: %v", err) - } - - nonce, err := newNonce() - if err != nil { - return nil, err - } - - return &cb.SignatureHeader{ - Creator: idBytes, - Nonce: nonce, - }, nil -} - -// newNonce generates a 24-byte nonce using the crypto/rand package. -func newNonce() ([]byte, error) { - nonce := make([]byte, 24) - - _, err := rand.Read(nonce) - if err != nil { - return nil, fmt.Errorf("failed to get random bytes: %v", err) - } - - return nonce, nil -} - // newChannelGroup defines the root of the channel configuration. func newChannelGroup(channelConfig Channel) (*cb.ConfigGroup, error) { var err error @@ -538,7 +442,7 @@ func newEnvelope( return env, nil } -// makeChannelHeader creates a ChannelHeader. +// channelHeader creates a ChannelHeader. func channelHeader(headerType cb.HeaderType, version int32, channelID string, epoch uint64) *cb.ChannelHeader { return &cb.ChannelHeader{ Type: int32(headerType), @@ -580,70 +484,6 @@ func concatenateBytes(data ...[]byte) []byte { return res } -// createSignedEnvelopeWithTLSBinding creates a signed envelope of the desired -// type, with marshaled dataMsg and signs it. It also includes a TLS cert hash -// into the channel header -func createSignedEnvelopeWithTLSBinding( - txType cb.HeaderType, - channelID string, - signingIdentity SigningIdentity, - envelope proto.Message, -) (*cb.Envelope, error) { - channelHeader := &cb.ChannelHeader{ - Type: int32(txType), - Timestamp: ×tamp.Timestamp{ - Seconds: time.Now().Unix(), - Nanos: 0, - }, - ChannelId: channelID, - } - - signatureHeader, err := signatureHeader(signingIdentity) - if err != nil { - return nil, fmt.Errorf("creating signature header: %v", err) - } - - cHeader, err := proto.Marshal(channelHeader) - if err != nil { - return nil, fmt.Errorf("marshaling channel header: %s", err) - } - - sHeader, err := proto.Marshal(signatureHeader) - if err != nil { - return nil, fmt.Errorf("marshaling signature header: %s", err) - } - - data, err := proto.Marshal(envelope) - if err != nil { - return nil, fmt.Errorf("marshaling config update envelope: %s", err) - } - - payload := &cb.Payload{ - Header: &cb.Header{ - ChannelHeader: cHeader, - SignatureHeader: sHeader, - }, - Data: data, - } - - payloadBytes, err := proto.Marshal(payload) - if err != nil { - return nil, fmt.Errorf("marshaling payload: %s", err) - } - - sig, err := signingIdentity.Sign(rand.Reader, payloadBytes, nil) - if err != nil { - return nil, fmt.Errorf("signing envelope's payload: %v", err) - } - - env := &cb.Envelope{ - Payload: payloadBytes, - Signature: sig, - } - - return env, nil -} - // unmarshalConfigValueAtKey unmarshals the value for the specified key in a config group // into the designated proto message. func unmarshalConfigValueAtKey(group *cb.ConfigGroup, key string, msg proto.Message) error { diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 16a48f22bd7..580fa35c9cd 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -29,10 +29,10 @@ func TestSignConfigUpdate(t *testing.T) { MSPID: "test-msp", } - configSignature, err := SignConfigUpdate(&cb.ConfigUpdate{}, signingIdentity) + configSignature, err := signingIdentity.SignConfigUpdate(&cb.ConfigUpdate{}) gt.Expect(err).NotTo(HaveOccurred()) - sh, err := signatureHeader(signingIdentity) + sh, err := signingIdentity.signatureHeader() gt.Expect(err).NotTo(HaveOccurred()) expectedCreator := sh.Creator signatureHeader := &cb.SignatureHeader{} @@ -425,11 +425,11 @@ func TestCreateSignedConfigUpdateEnvelope(t *testing.T) { configUpdate := &cb.ConfigUpdate{ ChannelId: "testchannel", } - configSignature, err := SignConfigUpdate(configUpdate, signingIdentity) + configSignature, err := signingIdentity.SignConfigUpdate(configUpdate) gt.Expect(err).NotTo(HaveOccurred()) // create signed config envelope - signedEnv, err := CreateSignedConfigUpdateEnvelope(configUpdate, signingIdentity, configSignature) + signedEnv, err := signingIdentity.SignConfigUpdateEnvelope(configUpdate, configSignature) gt.Expect(err).NotTo(HaveOccurred()) payload := &cb.Payload{} @@ -466,7 +466,7 @@ func TestCreateSignedConfigUpdateEnvelopeFailures(t *testing.T) { configUpdate := &cb.ConfigUpdate{ ChannelId: "testchannel", } - configSignature, err := SignConfigUpdate(configUpdate, signingIdentity) + configSignature, err := signingIdentity.SignConfigUpdate(configUpdate) gt.Expect(err).NotTo(HaveOccurred()) @@ -493,7 +493,7 @@ func TestCreateSignedConfigUpdateEnvelopeFailures(t *testing.T) { gt := NewGomegaWithT(t) // create signed config envelope - signedEnv, err := CreateSignedConfigUpdateEnvelope(tc.configUpdate, tc.signingIdentity, tc.configSignature...) + signedEnv, err := tc.signingIdentity.SignConfigUpdateEnvelope(tc.configUpdate, tc.configSignature...) gt.Expect(err).To(MatchError(tc.expectedErr)) gt.Expect(signedEnv).To(BeNil()) }) diff --git a/pkg/config/example_test.go b/pkg/config/example_test.go index eb527196090..cdde2652b08 100644 --- a/pkg/config/example_test.go +++ b/pkg/config/example_test.go @@ -150,7 +150,7 @@ func Example_systemChannel() { for _, si := range signingIdentities { // Sign the config update with the specified signer identity - configSignature, err := config.SignConfigUpdate(configUpdate, si) + configSignature, err := si.SignConfigUpdate(configUpdate) if err != nil { panic(err) } @@ -159,7 +159,7 @@ func Example_systemChannel() { } // Sign the envelope with the list of signatures - envelope, err := config.CreateSignedConfigUpdateEnvelope(configUpdate, peer1SigningIdentity, configSignatures...) + envelope, err := peer1SigningIdentity.SignConfigUpdateEnvelope(configUpdate, configSignatures...) if err != nil { panic(err) } diff --git a/pkg/config/signer.go b/pkg/config/signer.go index 281d5ef3adc..0dcce55dbf0 100644 --- a/pkg/config/signer.go +++ b/pkg/config/signer.go @@ -9,11 +9,17 @@ package config import ( "crypto" "crypto/ecdsa" + "crypto/rand" "crypto/x509" "encoding/asn1" + "encoding/pem" "fmt" "io" "math/big" + + "github.com/golang/protobuf/proto" + cb "github.com/hyperledger/fabric-protos-go/common" + mb "github.com/hyperledger/fabric-protos-go/msp" ) // SigningIdentity is an MSP Identity that can be used to sign configuration @@ -76,3 +82,145 @@ func toLowS(key ecdsa.PublicKey, sig ecdsaSignature) ecdsaSignature { return sig } + +// SignConfigUpdate signs the given configuration update with a +// specified signing identity and returns a config signature. +func (s *SigningIdentity) SignConfigUpdate(configUpdate *cb.ConfigUpdate) (*cb.ConfigSignature, error) { + signatureHeader, err := s.signatureHeader() + if err != nil { + return nil, fmt.Errorf("failed to create signature header: %v", err) + } + + header, err := proto.Marshal(signatureHeader) + if err != nil { + return nil, fmt.Errorf("marshaling signature header: %v", err) + } + + configSignature := &cb.ConfigSignature{ + SignatureHeader: header, + } + + configUpdateBytes, err := proto.Marshal(configUpdate) + if err != nil { + return nil, fmt.Errorf("marshaling config update: %v", err) + } + + configSignature.Signature, err = s.Sign( + rand.Reader, + concatenateBytes(configSignature.SignatureHeader, configUpdateBytes), + nil, + ) + if err != nil { + return nil, fmt.Errorf("failed to sign config update: %v", err) + } + + return configSignature, nil +} + +// SignConfigUpdateEnvelope creates a configuration update envelope and +// signs it using the SigningIdentity. +func (s *SigningIdentity) SignConfigUpdateEnvelope(configUpdate *cb.ConfigUpdate, signatures ...*cb.ConfigSignature) (*cb.Envelope, error) { + update, err := proto.Marshal(configUpdate) + if err != nil { + return nil, fmt.Errorf("marshaling config update: %v", err) + } + + configUpdateEnvelope := &cb.ConfigUpdateEnvelope{ + ConfigUpdate: update, + Signatures: signatures, + } + + signedEnvelope, err := s.createSignedEnvelope(cb.HeaderType_CONFIG_UPDATE, configUpdate.ChannelId, configUpdateEnvelope) + if err != nil { + return nil, fmt.Errorf("failed to create signed config update envelope: %v", err) + } + + return signedEnvelope, nil +} + +// createSignedEnvelope creates a signed envelope of the desired type and signs it. +func (s *SigningIdentity) createSignedEnvelope(txType cb.HeaderType, channelID string, envelope proto.Message) (*cb.Envelope, error) { + channelHeader := channelHeader(txType, msgVersion, channelID, epoch) + + signatureHeader, err := s.signatureHeader() + if err != nil { + return nil, fmt.Errorf("creating signature header: %v", err) + } + + cHeader, err := proto.Marshal(channelHeader) + if err != nil { + return nil, fmt.Errorf("marshaling channel header: %s", err) + } + + sHeader, err := proto.Marshal(signatureHeader) + if err != nil { + return nil, fmt.Errorf("marshaling signature header: %s", err) + } + + data, err := proto.Marshal(envelope) + if err != nil { + return nil, fmt.Errorf("marshaling config update envelope: %s", err) + } + + payload := &cb.Payload{ + Header: &cb.Header{ + ChannelHeader: cHeader, + SignatureHeader: sHeader, + }, + Data: data, + } + + payloadBytes, err := proto.Marshal(payload) + if err != nil { + return nil, fmt.Errorf("marshaling payload: %s", err) + } + + sig, err := s.Sign(rand.Reader, payloadBytes, nil) + if err != nil { + return nil, fmt.Errorf("signing envelope's payload: %v", err) + } + + env := &cb.Envelope{ + Payload: payloadBytes, + Signature: sig, + } + + return env, nil +} + +func (s *SigningIdentity) signatureHeader() (*cb.SignatureHeader, error) { + pemBytes := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: s.Certificate.Raw, + }) + + idBytes, err := proto.Marshal(&mb.SerializedIdentity{ + Mspid: s.MSPID, + IdBytes: pemBytes, + }) + if err != nil { + return nil, fmt.Errorf("marshaling serialized identity: %v", err) + } + + nonce, err := newNonce() + if err != nil { + return nil, err + } + + return &cb.SignatureHeader{ + Creator: idBytes, + Nonce: nonce, + }, nil +} + +// newNonce generates a 24-byte nonce using the crypto/rand package. +func newNonce() ([]byte, error) { + nonce := make([]byte, 24) + + _, err := rand.Read(nonce) + if err != nil { + return nil, fmt.Errorf("failed to get random bytes: %v", err) + } + + return nonce, nil +}