Skip to content

Commit

Permalink
[FAB-7542] add TLS cert hash to deliver client
Browse files Browse the repository at this point in the history
This change set makes the peer add its client TLS cert hash to
the channel header of the envelope it sends to the ordering service.

Change-Id: I5b41444dd0516b846ee351b51334941687d89a8a
Signed-off-by: yacovm <[email protected]>
  • Loading branch information
yacovm committed Dec 21, 2017
1 parent b38e0a9 commit 9dbcbb7
Show file tree
Hide file tree
Showing 9 changed files with 219 additions and 3 deletions.
5 changes: 5 additions & 0 deletions core/comm/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ func (cs *CredentialSupport) SetClientCertificate(cert tls.Certificate) {
cs.clientCert = cert
}

// GetClientCertificate returns the client certificate of the CredentialSupport
func (cs *CredentialSupport) GetClientCertificate() tls.Certificate {
return cs.clientCert
}

// GetDeliverServiceCredentials returns GRPC transport credentials for given channel to be used by GRPC
// clients which communicate with ordering service endpoints.
// If the channel isn't found, error is returned.
Expand Down
1 change: 1 addition & 0 deletions core/comm/connection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ func TestCredentialSupport(t *testing.T) {
cert := tls.Certificate{Certificate: [][]byte{}}
cs.SetClientCertificate(cert)
assert.Equal(t, cert, cs.clientCert)
assert.Equal(t, cert, cs.GetClientCertificate())

cs.AppRootCAsByChain["channel1"] = [][]byte{rootCAs[0]}
cs.AppRootCAsByChain["channel2"] = [][]byte{rootCAs[1]}
Expand Down
1 change: 1 addition & 0 deletions core/deliverservice/deliveryclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ func (d *deliverServiceImpl) Stop() {

func (d *deliverServiceImpl) newClient(chainID string, ledgerInfoProvider blocksprovider.LedgerInfo) *broadcastClient {
requester := &blocksRequester{
tls: comm.TLSEnabled(),
chainID: chainID,
}
broadcastSetup := func(bd blocksprovider.BlocksDeliverer) error {
Expand Down
16 changes: 14 additions & 2 deletions core/deliverservice/requester.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@ import (
"math"

"github.com/hyperledger/fabric/common/localmsp"
"github.com/hyperledger/fabric/common/util"
"github.com/hyperledger/fabric/core/comm"
"github.com/hyperledger/fabric/core/deliverservice/blocksprovider"
"github.com/hyperledger/fabric/protos/common"
"github.com/hyperledger/fabric/protos/orderer"
"github.com/hyperledger/fabric/protos/utils"
)

type blocksRequester struct {
tls bool
chainID string
client blocksprovider.BlocksDeliverer
}
Expand All @@ -53,6 +56,13 @@ func (b *blocksRequester) RequestBlocks(ledgerInfoProvider blocksprovider.Ledger
return nil
}

func (b *blocksRequester) getTLSCertHash() []byte {
if b.tls {
return util.ComputeSHA256(comm.GetCredentialSupport().GetClientCertificate().Certificate[0])
}
return nil
}

func (b *blocksRequester) seekOldest() error {
seekInfo := &orderer.SeekInfo{
Start: &orderer.SeekPosition{Type: &orderer.SeekPosition_Oldest{Oldest: &orderer.SeekOldest{}}},
Expand All @@ -63,7 +73,8 @@ func (b *blocksRequester) seekOldest() error {
//TODO- epoch and msgVersion may need to be obtained for nowfollowing usage in orderer/configupdate/configupdate.go
msgVersion := int32(0)
epoch := uint64(0)
env, err := utils.CreateSignedEnvelope(common.HeaderType_CONFIG_UPDATE, b.chainID, localmsp.NewSigner(), seekInfo, msgVersion, epoch)
tlsCertHash := b.getTLSCertHash()
env, err := utils.CreateSignedEnvelopeWithTLSBinding(common.HeaderType_CONFIG_UPDATE, b.chainID, localmsp.NewSigner(), seekInfo, msgVersion, epoch, tlsCertHash)
if err != nil {
return err
}
Expand All @@ -80,7 +91,8 @@ func (b *blocksRequester) seekLatestFromCommitter(height uint64) error {
//TODO- epoch and msgVersion may need to be obtained for nowfollowing usage in orderer/configupdate/configupdate.go
msgVersion := int32(0)
epoch := uint64(0)
env, err := utils.CreateSignedEnvelope(common.HeaderType_CONFIG_UPDATE, b.chainID, localmsp.NewSigner(), seekInfo, msgVersion, epoch)
tlsCertHash := b.getTLSCertHash()
env, err := utils.CreateSignedEnvelopeWithTLSBinding(common.HeaderType_CONFIG_UPDATE, b.chainID, localmsp.NewSigner(), seekInfo, msgVersion, epoch, tlsCertHash)
if err != nil {
return err
}
Expand Down
155 changes: 155 additions & 0 deletions core/deliverservice/requester_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package deliverclient

import (
"context"
"crypto/tls"
"crypto/x509"
"io/ioutil"
"path/filepath"
"testing"
"time"

"github.com/golang/protobuf/proto"
"github.com/hyperledger/fabric/core/comm"
"github.com/hyperledger/fabric/core/deliverservice/blocksprovider"
"github.com/hyperledger/fabric/protos/common"
"github.com/hyperledger/fabric/protos/orderer"
"github.com/hyperledger/fabric/protos/utils"
"github.com/stretchr/testify/assert"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)

func TestTLSBinding(t *testing.T) {
defer ensureNoGoroutineLeak(t)()

requester := blocksRequester{
tls: true,
chainID: "testchainid",
}

// Create an AtomicBroadcastServer
serverCert, serverKey, caCert := loadCertificates(t)
serverTLScert, err := tls.X509KeyPair(serverCert, serverKey)
assert.NoError(t, err)
comm.GetCredentialSupport().SetClientCertificate(serverTLScert)
s, err := comm.NewGRPCServer("localhost:9435", comm.ServerConfig{
SecOpts: &comm.SecureOptions{
RequireClientCert: true,
ServerKey: serverKey,
ServerCertificate: serverCert,
ClientRootCAs: [][]byte{caCert},
UseTLS: true,
},
})
assert.NoError(t, err)

orderer.RegisterAtomicBroadcastServer(s.Server(), &mockOrderer{})
go s.Start()
defer s.Stop()
time.Sleep(time.Second * 3)

// Create deliver client and attempt to request block 100
// from the ordering service
client := createClient(t, serverTLScert, caCert)
requester.client = client

// Test both seekLatestFromCommitter and seekOldest

// seekLatestFromCommitter
requester.seekLatestFromCommitter(100)
resp, err := requester.client.Recv()
assert.NoError(t, err)
assert.Equal(t, 100, int(resp.GetBlock().Header.Number))
client.conn.Close()

// seekOldest
client = createClient(t, serverTLScert, caCert)
requester.client = client
requester.seekOldest()
resp, err = requester.client.Recv()
assert.NoError(t, err)
assert.Equal(t, 100, int(resp.GetBlock().Header.Number))
client.conn.Close()
}

func loadCertificates(t *testing.T) (cert []byte, key []byte, caCert []byte) {
var err error
caCertFile := filepath.Join("testdata", "ca.pem")
certFile := filepath.Join("testdata", "cert.pem")
keyFile := filepath.Join("testdata", "key.pem")

cert, err = ioutil.ReadFile(certFile)
assert.NoError(t, err)
key, err = ioutil.ReadFile(keyFile)
assert.NoError(t, err)
caCert, err = ioutil.ReadFile(caCertFile)
assert.NoError(t, err)
return
}

type mockClient struct {
blocksprovider.BlocksDeliverer
conn *grpc.ClientConn
}

func createClient(t *testing.T, tlsCert tls.Certificate, caCert []byte) *mockClient {
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{tlsCert},
RootCAs: x509.NewCertPool(),
}
tlsConfig.RootCAs.AppendCertsFromPEM(caCert)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
dialOpts := []grpc.DialOption{grpc.WithBlock(), grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))}
conn, err := grpc.DialContext(ctx, "localhost:9435", dialOpts...)
assert.NoError(t, err)
cl := orderer.NewAtomicBroadcastClient(conn)

stream, err := cl.Deliver(context.Background())
assert.NoError(t, err)
return &mockClient{
conn: conn,
BlocksDeliverer: stream,
}
}

type mockOrderer struct {
t *testing.T
}

func (*mockOrderer) Broadcast(orderer.AtomicBroadcast_BroadcastServer) error {
panic("not implemented")
}

func (o *mockOrderer) Deliver(stream orderer.AtomicBroadcast_DeliverServer) error {
env, _ := stream.Recv()
inspectTLSBinding := comm.NewBindingInspector(true, func(msg proto.Message) []byte {
env, isEnvelope := msg.(*common.Envelope)
if !isEnvelope || env == nil {
assert.Fail(o.t, "not an envelope")
}
ch, err := utils.ChannelHeader(env)
assert.NoError(o.t, err)
return ch.TlsCertHash
})
err := inspectTLSBinding(stream.Context(), env)
assert.NoError(o.t, err, "orderer rejected TLS binding")

stream.Send(&orderer.DeliverResponse{
Type: &orderer.DeliverResponse_Block{
Block: &common.Block{
Header: &common.BlockHeader{
Number: 100,
},
},
},
})
return nil
}
15 changes: 15 additions & 0 deletions core/deliverservice/testdata/ca.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-----BEGIN CERTIFICATE-----
MIICSTCCAe+gAwIBAgIQZMqAAhpj/lLHsJeIp1nJ7zAKBggqhkjOPQQDAjB2MQsw
CQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy
YW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEfMB0GA1UEAxMWdGxz
Y2Eub3JnMS5leGFtcGxlLmNvbTAeFw0xNzExMTcyMjM4NTZaFw0yNzExMTUyMjM4
NTZaMHYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH
Ew1TYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMR8wHQYD
VQQDExZ0bHNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D
AQcDQgAEMDe9E6+fydjWG40IHBnS1sZh4Mpw4G1KCWd4plTPZb0qJ7YaLkARx2dm
d65FGh7dhJGUBQTNWa3/cLVB28tQVaNfMF0wDgYDVR0PAQH/BAQDAgGmMA8GA1Ud
JQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zApBgNVHQ4EIgQgLzJgBSiEH1GF
M+2iuJA92mp7j0SPqxmRmYyfbI+1+3wwCgYIKoZIzj0EAwIDSAAwRQIhAIEXLz9u
XpAt1nXTuEVYAJYipi6TYtSnsOB/teMxZ887AiAs5IU1lWKYk4/RXrU9NgNdrUs+
hLygondsbVWt6bKZzg==
-----END CERTIFICATE-----
16 changes: 16 additions & 0 deletions core/deliverservice/testdata/cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE-----
MIICdDCCAhqgAwIBAgIRAK5HIE/tumHtKRObBKPvnYQwCgYIKoZIzj0EAwIwdjEL
MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG
cmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHzAdBgNVBAMTFnRs
c2NhLm9yZzEuZXhhbXBsZS5jb20wHhcNMTcxMTE3MjIzODU2WhcNMjcxMTE1MjIz
ODU2WjBfMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UE
BxMNU2FuIEZyYW5jaXNjbzEjMCEGA1UEAxMabG9jYWxob3N0Lm9yZzEuZXhhbXBs
ZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASyPS7GHgSfNsvB8X5d8WT2
91Z1AG+Ie5OpkUtI4Cmqq4lTUz+ba1f22EftkP8AsvO3NV6EBPsTNnUgqwORk4Lu
o4GfMIGcMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB
BQUHAwIwDAYDVR0TAQH/BAIwADArBgNVHSMEJDAigCAvMmAFKIQfUYUz7aK4kD3a
anuPRI+rGZGZjJ9sj7X7fDAwBgNVHREEKTAnghpsb2NhbGhvc3Qub3JnMS5leGFt
cGxlLmNvbYIJbG9jYWxob3N0MAoGCCqGSM49BAMCA0gAMEUCIQDAYC/+I9f3Z8rk
bUmmZojIcf+VKtt2r/Ws2gurw/OxSgIgKevKSlauM5DlLDvaJVgsbQhxmoUL/oyt
3bQu7kpCZ0k=
-----END CERTIFICATE-----
5 changes: 5 additions & 0 deletions core/deliverservice/testdata/key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg4fKabn/wDrH9CNFt
p41HuWqRZEalrLk0mwkVt42dCWahRANCAASyPS7GHgSfNsvB8X5d8WT291Z1AG+I
e5OpkUtI4Cmqq4lTUz+ba1f22EftkP8AsvO3NV6EBPsTNnUgqwORk4Lu
-----END PRIVATE KEY-----
8 changes: 7 additions & 1 deletion protos/utils/txutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,14 @@ func GetEnvelopeFromBlock(data []byte) (*common.Envelope, error) {

// CreateSignedEnvelope creates a signed envelope of the desired type, with marshaled dataMsg and signs it
func CreateSignedEnvelope(txType common.HeaderType, channelID string, signer crypto.LocalSigner, dataMsg proto.Message, msgVersion int32, epoch uint64) (*common.Envelope, error) {
payloadChannelHeader := MakeChannelHeader(txType, msgVersion, channelID, epoch)
return CreateSignedEnvelopeWithTLSBinding(txType, channelID, signer, dataMsg, msgVersion, epoch, nil)
}

// 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 common.HeaderType, channelID string, signer crypto.LocalSigner, dataMsg proto.Message, msgVersion int32, epoch uint64, tlsCertHash []byte) (*common.Envelope, error) {
payloadChannelHeader := MakeChannelHeader(txType, msgVersion, channelID, epoch)
payloadChannelHeader.TlsCertHash = tlsCertHash
var err error
payloadSignatureHeader := &common.SignatureHeader{}

Expand Down

0 comments on commit 9dbcbb7

Please sign in to comment.