From 7c09f2e4aa172e734e9db9d8330caedd06c18c2b Mon Sep 17 00:00:00 2001 From: Troy Ronda Date: Thu, 21 Dec 2017 08:00:02 -0500 Subject: [PATCH] [FAB-7536] Provide TLS Cert Hash in Channel Header Change-Id: Id6bf5024543b4e7957a511d5e45d3a3f9e7c917f Signed-off-by: Troy Ronda --- pkg/config/comm/comm.go | 17 +++++++ pkg/config/comm/comm_test.go | 56 +++++++++++++++++++++ pkg/config/comm/testdata/server.crt | 15 ++++++ pkg/config/comm/testdata/server.key | 5 ++ pkg/fabric-client/channel/block.go | 7 ++- pkg/fabric-client/channel/txnsender.go | 13 ++--- pkg/fabric-client/channel/txnsender_test.go | 2 +- pkg/fabric-client/client.go | 4 +- 8 files changed, 109 insertions(+), 10 deletions(-) create mode 100644 pkg/config/comm/testdata/server.crt create mode 100644 pkg/config/comm/testdata/server.key diff --git a/pkg/config/comm/comm.go b/pkg/config/comm/comm.go index 3410f9f321..daa7a9eafe 100644 --- a/pkg/config/comm/comm.go +++ b/pkg/config/comm/comm.go @@ -10,6 +10,7 @@ import ( "crypto/tls" "github.com/hyperledger/fabric-sdk-go/api/apiconfig" + cutil "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric/common/util" "github.com/hyperledger/fabric-sdk-go/pkg/errors" ) @@ -34,3 +35,19 @@ func TLSConfig(certificate string, serverhostoverride string, config apiconfig.C return &tls.Config{RootCAs: tlsCaCertPool, Certificates: clientCerts, ServerName: serverhostoverride}, nil } + +// TLSCertHash is a utility method to calculate the SHA256 hash of the configured certificate (for usage in channel headers) +func TLSCertHash(config apiconfig.Config) []byte { + certs, err := config.TLSClientCerts() + if err != nil || len(certs) == 0 { + return nil + } + + cert := certs[0] + if len(cert.Certificate) == 0 { + return nil + } + + h := cutil.ComputeSHA256(cert.Certificate[0]) + return h +} diff --git a/pkg/config/comm/comm_test.go b/pkg/config/comm/comm_test.go index f4a71b5ef4..474e97506e 100644 --- a/pkg/config/comm/comm_test.go +++ b/pkg/config/comm/comm_test.go @@ -7,7 +7,9 @@ SPDX-License-Identifier: Apache-2.0 package comm import ( + "bytes" "crypto/x509" + "encoding/hex" "testing" "strings" @@ -122,3 +124,57 @@ func TestTLSConfigHappyPath(t *testing.T) { t.Fatal("Certs do not match") } } + +func TestNoTlsCertHash(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + config := mock_apiconfig.NewMockConfig(mockCtrl) + + config.EXPECT().TLSClientCerts().Return([]tls.Certificate{}, nil) + + tlsCertHash := TLSCertHash(config) + + if len(tlsCertHash) != 0 { + t.Fatal("Unexpected non-empty cert hash") + } +} + +func TestEmptyTlsCertHash(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + config := mock_apiconfig.NewMockConfig(mockCtrl) + + emptyCert := tls.Certificate{} + config.EXPECT().TLSClientCerts().Return([]tls.Certificate{emptyCert}, nil) + + tlsCertHash := TLSCertHash(config) + + if len(tlsCertHash) != 0 { + t.Fatal("Unexpected non-empty cert hash") + } +} + +func TestTlsCertHash(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + config := mock_apiconfig.NewMockConfig(mockCtrl) + + cert, err := tls.LoadX509KeyPair("testdata/server.crt", "testdata/server.key") + if err != nil { + t.Fatalf("Unexpected error loading cert %v", err) + } + + config.EXPECT().TLSClientCerts().Return([]tls.Certificate{cert}, nil) + tlsCertHash := TLSCertHash(config) + + // openssl x509 -fingerprint -sha256 -in testdata/server.crt + // SHA256 Fingerprint=0D:D5:90:B8:A5:0E:A6:04:3E:A8:75:16:BF:77:A8:FE:E7:C5:62:2D:4C:B3:CB:99:12:74:72:2A:D8:BA:B8:92 + expectedHash, err := hex.DecodeString("0DD590B8A50EA6043EA87516BF77A8FEE7C5622D4CB3CB991274722AD8BAB892") + if err != nil { + t.Fatalf("Unexpected error decoding cert fingerprint %v", err) + } + + if bytes.Compare(tlsCertHash, expectedHash) != 0 { + t.Fatal("Cert hash calculated incorrectly") + } +} diff --git a/pkg/config/comm/testdata/server.crt b/pkg/config/comm/testdata/server.crt new file mode 100644 index 0000000000..177bc172f0 --- /dev/null +++ b/pkg/config/comm/testdata/server.crt @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICZjCCAg2gAwIBAgIQLl+Ug2jLrky26o8cOZE9MjAKBggqhkjOPQQDAjB2MQsw +CQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy +YW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEfMB0GA1UEAxMWdGxz +Y2Eub3JnMS5leGFtcGxlLmNvbTAeFw0xNzA3MjgxNDI3MjBaFw0yNzA3MjYxNDI3 +MjBaMFsxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH +Ew1TYW4gRnJhbmNpc2NvMR8wHQYDVQQDExZwZWVyMC5vcmcxLmV4YW1wbGUuY29t +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAhDbBctALpd2ushdz8/cng3hnPE9 +YFkZa0QIkhBzuPZE7iH8JpB/e5d1zz6IsN0fY5FsvjZsvWkCj8xrl+eePaOBlzCB +lDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC +MAwGA1UdEwEB/wQCMAAwKwYDVR0jBCQwIoAgP2onPvGFmShXzjY5WPEJhhDAKM8h +j7l705FH7ynFXNwwKAYDVR0RBCEwH4IWcGVlcjAub3JnMS5leGFtcGxlLmNvbYIF +cGVlcjAwCgYIKoZIzj0EAwIDRwAwRAIgKvld7K1+MukbhULYuhVRZsy4UqpdZ3Iu ++UEU4Vs/CDwCIEuzQaX2N0gnf4UziPBAlrJGArocYA27CpSQX7ycmzJh +-----END CERTIFICATE----- diff --git a/pkg/config/comm/testdata/server.key b/pkg/config/comm/testdata/server.key new file mode 100644 index 0000000000..759aa285ec --- /dev/null +++ b/pkg/config/comm/testdata/server.key @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgIHcvk45pDqakyZY6 +81ZlopbYflQUbcnYUvjFJSoRJfShRANCAAQCENsFy0Aul3a6yF3Pz9yeDeGc8T1g +WRlrRAiSEHO49kTuIfwmkH97l3XPPoiw3R9jkWy+Nmy9aQKPzGuX5549 +-----END PRIVATE KEY----- diff --git a/pkg/fabric-client/channel/block.go b/pkg/fabric-client/channel/block.go index a1b43a2119..0ff3e0c517 100644 --- a/pkg/fabric-client/channel/block.go +++ b/pkg/fabric-client/channel/block.go @@ -16,6 +16,7 @@ import ( protos_utils "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric/protos/utils" "github.com/hyperledger/fabric-sdk-go/third_party/github.com/hyperledger/fabric/protos/common" + ccomm "github.com/hyperledger/fabric-sdk-go/pkg/config/comm" "github.com/hyperledger/fabric-sdk-go/pkg/errors" fc "github.com/hyperledger/fabric-sdk-go/pkg/fabric-client/internal" ) @@ -61,7 +62,8 @@ func (c *Channel) GenesisBlock(request *fab.GenesisBlockRequest) (*common.Block, Behavior: ab.SeekInfo_BLOCK_UNTIL_READY, } protos_utils.MakeChannelHeader(common.HeaderType_DELIVER_SEEK_INFO, 1, c.Name(), 0) - seekInfoHeader, err := BuildChannelHeader(common.HeaderType_DELIVER_SEEK_INFO, c.Name(), request.TxnID.ID, 0, "", time.Now()) + tlsCertHash := ccomm.TLSCertHash(c.clientContext.Config()) + seekInfoHeader, err := BuildChannelHeader(common.HeaderType_DELIVER_SEEK_INFO, c.Name(), request.TxnID.ID, 0, "", time.Now(), tlsCertHash) if err != nil { return nil, errors.Wrap(err, "BuildChannelHeader failed") } @@ -107,7 +109,8 @@ func (c *Channel) block(pos *ab.SeekPosition) (*common.Block, error) { return nil, errors.Wrap(err, "generating TX ID failed") } - seekInfoHeader, err := BuildChannelHeader(common.HeaderType_DELIVER_SEEK_INFO, c.Name(), txID, 0, "", time.Now()) + tlsCertHash := ccomm.TLSCertHash(c.clientContext.Config()) + seekInfoHeader, err := BuildChannelHeader(common.HeaderType_DELIVER_SEEK_INFO, c.Name(), txID, 0, "", time.Now(), tlsCertHash) if err != nil { return nil, errors.Wrap(err, "BuildChannelHeader failed") } diff --git a/pkg/fabric-client/channel/txnsender.go b/pkg/fabric-client/channel/txnsender.go index a0241ad573..e69962ddd7 100644 --- a/pkg/fabric-client/channel/txnsender.go +++ b/pkg/fabric-client/channel/txnsender.go @@ -390,14 +390,15 @@ func (c *Channel) SendEnvelope(envelope *fab.SignedEnvelope) (*common.Block, err } // BuildChannelHeader is a utility method to build a common chain header (TODO refactor) -func BuildChannelHeader(headerType common.HeaderType, channelID string, txID string, epoch uint64, chaincodeID string, timestamp time.Time) (*common.ChannelHeader, error) { +func BuildChannelHeader(headerType common.HeaderType, channelID string, txID string, epoch uint64, chaincodeID string, timestamp time.Time, tlsCertHash []byte) (*common.ChannelHeader, error) { logger.Debugf("buildChannelHeader - headerType: %s channelID: %s txID: %d epoch: % chaincodeID: %s timestamp: %v", headerType, channelID, txID, epoch, chaincodeID, timestamp) channelHeader := &common.ChannelHeader{ - Type: int32(headerType), - Version: 1, - ChannelId: channelID, - TxId: txID, - Epoch: epoch, + Type: int32(headerType), + Version: 1, + ChannelId: channelID, + TxId: txID, + Epoch: epoch, + TlsCertHash: tlsCertHash, } ts, err := ptypes.TimestampProto(timestamp) diff --git a/pkg/fabric-client/channel/txnsender_test.go b/pkg/fabric-client/channel/txnsender_test.go index d17b86fd1a..4540290d4e 100644 --- a/pkg/fabric-client/channel/txnsender_test.go +++ b/pkg/fabric-client/channel/txnsender_test.go @@ -435,7 +435,7 @@ func TestSendTransaction(t *testing.T) { func TestBuildChannelHeader(t *testing.T) { - header, err := BuildChannelHeader(common.HeaderType_CHAINCODE_PACKAGE, "test", "", 1, "1234", time.Time{}) + header, err := BuildChannelHeader(common.HeaderType_CHAINCODE_PACKAGE, "test", "", 1, "1234", time.Time{}, []byte{}) if err != nil || header == nil { t.Fatalf("Test Build Channel Header failed, cause : '%s'", err.Error()) diff --git a/pkg/fabric-client/client.go b/pkg/fabric-client/client.go index 54e99f518a..dc7509059b 100644 --- a/pkg/fabric-client/client.go +++ b/pkg/fabric-client/client.go @@ -23,6 +23,7 @@ import ( "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric/common/crypto" fcutils "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric/common/util" protos_utils "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric/protos/utils" + ccomm "github.com/hyperledger/fabric-sdk-go/pkg/config/comm" "github.com/hyperledger/fabric-sdk-go/pkg/errors" channel "github.com/hyperledger/fabric-sdk-go/pkg/fabric-client/channel" "github.com/hyperledger/fabric-sdk-go/pkg/fabric-client/identity" @@ -386,7 +387,8 @@ func (c *Client) createOrUpdateChannel(request fab.CreateChannelRequest, haveEnv } // TODO: Move - channelHeader, err := channel.BuildChannelHeader(common.HeaderType_CONFIG_UPDATE, request.Name, request.TxnID.ID, 0, "", time.Now()) + tlsCertHash := ccomm.TLSCertHash(c.config) + channelHeader, err := channel.BuildChannelHeader(common.HeaderType_CONFIG_UPDATE, request.Name, request.TxnID.ID, 0, "", time.Now(), tlsCertHash) if err != nil { return errors.WithMessage(err, "BuildChannelHeader failed") }