Skip to content

Commit a950854

Browse files
committed
FAB-1224 Gossip mutual TLS + better bindings
This commit makes the gossip comm layer take advantage of mutual TLS (client authentication) and changes the handshake from signing on the TLS-Unique to sign the hash of the certificate in both sides (client and server). https://jira.hyperledger.org/browse/FAB-1224 Signed-off-by: Yacov Manevich <[email protected]> Change-Id: Ie60153ced2ab0d4c1415ae047a7b438decb04165
1 parent f9b68d4 commit a950854

File tree

6 files changed

+139
-89
lines changed

6 files changed

+139
-89
lines changed

gossip/comm/comm_impl.go

+47-14
Original file line numberDiff line numberDiff line change
@@ -72,17 +72,19 @@ func NewCommInstanceWithServer(port int, idMapper identity.Mapper, peerIdentity
7272
var ll net.Listener
7373
var s *grpc.Server
7474
var secOpt grpc.DialOption
75+
var certHash []byte
7576

7677
if len(dialOpts) == 0 {
7778
dialOpts = []grpc.DialOption{grpc.WithTimeout(dialTimeout)}
7879
}
7980

8081
if port > 0 {
81-
s, ll, secOpt = createGRPCLayer(port)
82+
s, ll, secOpt, certHash = createGRPCLayer(port)
8283
dialOpts = append(dialOpts, secOpt)
8384
}
8485

8586
commInst := &commImpl{
87+
selfCertHash: certHash,
8688
PKIID: idMapper.GetPKIidOfCert(peerIdentity),
8789
idMapper: idMapper,
8890
logger: util.GetLogger(util.LOGGING_COMM_MODULE, fmt.Sprintf("%d", port)),
@@ -117,16 +119,28 @@ func NewCommInstanceWithServer(port int, idMapper identity.Mapper, peerIdentity
117119
}
118120

119121
// NewCommInstance creates a new comm instance that binds itself to the given gRPC server
120-
func NewCommInstance(s *grpc.Server, idStore identity.Mapper, peerIdentity api.PeerIdentityType, dialOpts ...grpc.DialOption) (Comm, error) {
122+
func NewCommInstance(s *grpc.Server, cert *tls.Certificate, idStore identity.Mapper, peerIdentity api.PeerIdentityType, dialOpts ...grpc.DialOption) (Comm, error) {
121123
commInst, err := NewCommInstanceWithServer(-1, idStore, peerIdentity, dialOpts...)
122124
if err != nil {
123125
return nil, err
124126
}
127+
128+
if cert != nil {
129+
inst := commInst.(*commImpl)
130+
if len(cert.Certificate) == 0 {
131+
inst.logger.Panic("Certificate supplied but certificate chain is empty")
132+
} else {
133+
inst.selfCertHash = certHashFromRawCert(cert.Certificate[0])
134+
}
135+
}
136+
125137
proto.RegisterGossipServer(s, commInst.(*commImpl))
138+
126139
return commInst, nil
127140
}
128141

129142
type commImpl struct {
143+
selfCertHash []byte
130144
peerIdentity api.PeerIdentityType
131145
idMapper identity.Mapper
132146
logger *util.Logger
@@ -373,13 +387,16 @@ func extractRemoteAddress(stream stream) string {
373387
func (c *commImpl) authenticateRemotePeer(stream stream) (common.PKIidType, error) {
374388
ctx := stream.Context()
375389
remoteAddress := extractRemoteAddress(stream)
376-
tlsUnique := ExtractTLSUnique(ctx)
390+
remoteCertHash := extractCertificateHashFromContext(ctx)
377391
var sig []byte
378392
var err error
379-
if tlsUnique != nil {
380-
sig, err = c.idMapper.Sign(tlsUnique)
393+
394+
// If TLS is detected, sign the hash of our cert to bind our TLS cert
395+
// to the gRPC session
396+
if remoteCertHash != nil && c.selfCertHash != nil {
397+
sig, err = c.idMapper.Sign(c.selfCertHash)
381398
if err != nil {
382-
c.logger.Error("Failed signing TLS-Unique:", err)
399+
c.logger.Error("Failed signing self certificate hash:", err)
383400
return nil, err
384401
}
385402
}
@@ -414,8 +431,9 @@ func (c *commImpl) authenticateRemotePeer(stream stream) (common.PKIidType, erro
414431
return nil, err
415432
}
416433

417-
if tlsUnique != nil {
418-
err = c.idMapper.Verify(receivedMsg.PkiID, receivedMsg.Sig, tlsUnique)
434+
// if TLS is detected, verify remote peer
435+
if remoteCertHash != nil && c.selfCertHash != nil {
436+
err = c.idMapper.Verify(receivedMsg.PkiID, receivedMsg.Sig, remoteCertHash)
419437
if err != nil {
420438
c.logger.Error("Failed verifying signature from", remoteAddress, ":", err)
421439
return nil, err
@@ -424,7 +442,6 @@ func (c *commImpl) authenticateRemotePeer(stream stream) (common.PKIidType, erro
424442

425443
c.logger.Debug("Authenticated", remoteAddress)
426444
return receivedMsg.PkiID, nil
427-
428445
}
429446

430447
func (c *commImpl) GossipStream(stream proto.Gossip_GossipStreamServer) error {
@@ -518,7 +535,8 @@ type stream interface {
518535
grpc.Stream
519536
}
520537

521-
func createGRPCLayer(port int) (*grpc.Server, net.Listener, grpc.DialOption) {
538+
func createGRPCLayer(port int) (*grpc.Server, net.Listener, grpc.DialOption, []byte) {
539+
var returnedCertHash []byte
522540
var s *grpc.Server
523541
var ll net.Listener
524542
var err error
@@ -533,10 +551,25 @@ func createGRPCLayer(port int) (*grpc.Server, net.Listener, grpc.DialOption) {
533551

534552
err = generateCertificates(keyFileName, certFileName)
535553
if err == nil {
536-
var creds credentials.TransportCredentials
537-
creds, err = credentials.NewServerTLSFromFile(certFileName, keyFileName)
538-
serverOpts = append(serverOpts, grpc.Creds(creds))
554+
cert, err := tls.LoadX509KeyPair(certFileName, keyFileName)
555+
if err != nil {
556+
panic(err)
557+
}
558+
559+
if len(cert.Certificate) == 0 {
560+
panic(fmt.Errorf("Certificate chain is nil"))
561+
}
562+
563+
returnedCertHash = certHashFromRawCert(cert.Certificate[0])
564+
565+
tlsConf := &tls.Config{
566+
Certificates: []tls.Certificate{cert},
567+
ClientAuth: tls.RequestClientCert,
568+
InsecureSkipVerify: true,
569+
}
570+
serverOpts = append(serverOpts, grpc.Creds(credentials.NewTLS(tlsConf)))
539571
ta := credentials.NewTLS(&tls.Config{
572+
Certificates: []tls.Certificate{cert},
540573
InsecureSkipVerify: true,
541574
})
542575
dialOpts = grpc.WithTransportCredentials(&authCreds{tlsCreds: ta})
@@ -551,5 +584,5 @@ func createGRPCLayer(port int) (*grpc.Server, net.Listener, grpc.DialOption) {
551584
}
552585

553586
s = grpc.NewServer(serverOpts...)
554-
return s, ll, dialOpts
587+
return s, ll, dialOpts, returnedCertHash
555588
}

gossip/comm/comm_test.go

+11-5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"crypto/tls"
2222
"fmt"
2323
"math/rand"
24+
"os"
2425
"sync"
2526
"testing"
2627
"time"
@@ -88,8 +89,13 @@ func newCommInstance(port int, sec api.MessageCryptoService) (Comm, error) {
8889
}
8990

9091
func handshaker(endpoint string, comm Comm, t *testing.T, sigMutator func([]byte) []byte, pkiIDmutator func([]byte) []byte) <-chan ReceivedMessage {
92+
err := generateCertificates("key.pem", "cert.pem")
93+
defer os.Remove("cert.pem")
94+
defer os.Remove("key.pem")
95+
cert, err := tls.LoadX509KeyPair("cert.pem", "key.pem")
9196
ta := credentials.NewTLS(&tls.Config{
9297
InsecureSkipVerify: true,
98+
Certificates: []tls.Certificate{cert},
9399
})
94100
acceptChan := comm.Accept(acceptAll)
95101
conn, err := grpc.Dial("localhost:9611", grpc.WithTransportCredentials(&authCreds{tlsCreds: ta}), grpc.WithBlock(), grpc.WithTimeout(time.Second))
@@ -103,8 +109,8 @@ func handshaker(endpoint string, comm Comm, t *testing.T, sigMutator func([]byte
103109
if err != nil {
104110
return nil
105111
}
106-
clientTLSUnique := ExtractTLSUnique(stream.Context())
107-
sig, err := naiveSec.Sign(clientTLSUnique)
112+
clientCertHash := certHashFromRawCert(cert.Certificate[0])
113+
sig, err := naiveSec.Sign(clientCertHash)
108114
if sigMutator != nil {
109115
sig = sigMutator(sig)
110116
}
@@ -119,7 +125,7 @@ func handshaker(endpoint string, comm Comm, t *testing.T, sigMutator func([]byte
119125
msg, err = stream.Recv()
120126
assert.NoError(t, err, "%v", err)
121127
if sigMutator == nil {
122-
assert.Equal(t, clientTLSUnique, msg.GetConn().Sig)
128+
assert.Equal(t, extractCertificateHashFromContext(stream.Context()), msg.GetConn().Sig)
123129
}
124130
assert.Equal(t, []byte("localhost:9611"), msg.GetConn().PkiID)
125131
msg2Send := createGossipMsg()
@@ -152,10 +158,10 @@ func TestHandshake(t *testing.T) {
152158
assert.Equal(t, 0, len(acceptChan))
153159

154160
// negative path, nothing should be read from the channel because the PKIid doesn't match the identity
155-
mutateEndpoint := func(b []byte) []byte {
161+
mutatePKIID := func(b []byte) []byte {
156162
return []byte("localhost:9650")
157163
}
158-
acceptChan = handshaker("localhost:9613", comm, t, nil, mutateEndpoint)
164+
acceptChan = handshaker("localhost:9613", comm, t, nil, mutatePKIID)
159165
time.Sleep(time.Second)
160166
assert.Equal(t, 0, len(acceptChan))
161167
}

gossip/comm/crypto.go

+20-6
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ import (
2020
"crypto/ecdsa"
2121
"crypto/elliptic"
2222
"crypto/rand"
23+
"crypto/sha256"
24+
"crypto/tls"
2325
"crypto/x509"
2426
"encoding/pem"
2527
"math/big"
26-
"os"
27-
28-
"crypto/tls"
2928
"net"
29+
"os"
3030
"time"
3131

3232
"golang.org/x/net/context"
@@ -71,8 +71,17 @@ func generateCertificates(privKeyFile string, certKeyFile string) error {
7171
return err
7272
}
7373

74-
// ExtractTLSUnique extracts the TLS-Unique from the stream
75-
func ExtractTLSUnique(ctx context.Context) []byte {
74+
func certHashFromRawCert(rawCert []byte) []byte {
75+
if len(rawCert) == 0 {
76+
return nil
77+
}
78+
hash := sha256.New()
79+
hash.Write(rawCert)
80+
return hash.Sum(nil)
81+
}
82+
83+
// ExtractCertificateHash extracts the hash of the certificate from the stream
84+
func extractCertificateHashFromContext(ctx context.Context) []byte {
7685
pr, extracted := peer.FromContext(ctx)
7786
if !extracted {
7887
return nil
@@ -87,7 +96,12 @@ func ExtractTLSUnique(ctx context.Context) []byte {
8796
if !isTLSConn {
8897
return nil
8998
}
90-
return tlsInfo.State.TLSUnique
99+
certs := tlsInfo.State.PeerCertificates
100+
if len(certs) == 0 {
101+
return nil
102+
}
103+
raw := certs[0].Raw
104+
return certHashFromRawCert(raw)
91105
}
92106

93107
type authCreds struct {

0 commit comments

Comments
 (0)