Skip to content

Commit

Permalink
FAB-1224 Gossip mutual TLS + better bindings
Browse files Browse the repository at this point in the history
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 <yacovm@il.ibm.com>
Change-Id: Ie60153ced2ab0d4c1415ae047a7b438decb04165
  • Loading branch information
yacovm committed Dec 21, 2016
1 parent f9b68d4 commit a950854
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 89 deletions.
61 changes: 47 additions & 14 deletions gossip/comm/comm_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,19 @@ func NewCommInstanceWithServer(port int, idMapper identity.Mapper, peerIdentity
var ll net.Listener
var s *grpc.Server
var secOpt grpc.DialOption
var certHash []byte

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

if port > 0 {
s, ll, secOpt = createGRPCLayer(port)
s, ll, secOpt, certHash = createGRPCLayer(port)
dialOpts = append(dialOpts, secOpt)
}

commInst := &commImpl{
selfCertHash: certHash,
PKIID: idMapper.GetPKIidOfCert(peerIdentity),
idMapper: idMapper,
logger: util.GetLogger(util.LOGGING_COMM_MODULE, fmt.Sprintf("%d", port)),
Expand Down Expand Up @@ -117,16 +119,28 @@ func NewCommInstanceWithServer(port int, idMapper identity.Mapper, peerIdentity
}

// NewCommInstance creates a new comm instance that binds itself to the given gRPC server
func NewCommInstance(s *grpc.Server, idStore identity.Mapper, peerIdentity api.PeerIdentityType, dialOpts ...grpc.DialOption) (Comm, error) {
func NewCommInstance(s *grpc.Server, cert *tls.Certificate, idStore identity.Mapper, peerIdentity api.PeerIdentityType, dialOpts ...grpc.DialOption) (Comm, error) {
commInst, err := NewCommInstanceWithServer(-1, idStore, peerIdentity, dialOpts...)
if err != nil {
return nil, err
}

if cert != nil {
inst := commInst.(*commImpl)
if len(cert.Certificate) == 0 {
inst.logger.Panic("Certificate supplied but certificate chain is empty")
} else {
inst.selfCertHash = certHashFromRawCert(cert.Certificate[0])
}
}

proto.RegisterGossipServer(s, commInst.(*commImpl))

return commInst, nil
}

type commImpl struct {
selfCertHash []byte
peerIdentity api.PeerIdentityType
idMapper identity.Mapper
logger *util.Logger
Expand Down Expand Up @@ -373,13 +387,16 @@ func extractRemoteAddress(stream stream) string {
func (c *commImpl) authenticateRemotePeer(stream stream) (common.PKIidType, error) {
ctx := stream.Context()
remoteAddress := extractRemoteAddress(stream)
tlsUnique := ExtractTLSUnique(ctx)
remoteCertHash := extractCertificateHashFromContext(ctx)
var sig []byte
var err error
if tlsUnique != nil {
sig, err = c.idMapper.Sign(tlsUnique)

// If TLS is detected, sign the hash of our cert to bind our TLS cert
// to the gRPC session
if remoteCertHash != nil && c.selfCertHash != nil {
sig, err = c.idMapper.Sign(c.selfCertHash)
if err != nil {
c.logger.Error("Failed signing TLS-Unique:", err)
c.logger.Error("Failed signing self certificate hash:", err)
return nil, err
}
}
Expand Down Expand Up @@ -414,8 +431,9 @@ func (c *commImpl) authenticateRemotePeer(stream stream) (common.PKIidType, erro
return nil, err
}

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

c.logger.Debug("Authenticated", remoteAddress)
return receivedMsg.PkiID, nil

}

func (c *commImpl) GossipStream(stream proto.Gossip_GossipStreamServer) error {
Expand Down Expand Up @@ -518,7 +535,8 @@ type stream interface {
grpc.Stream
}

func createGRPCLayer(port int) (*grpc.Server, net.Listener, grpc.DialOption) {
func createGRPCLayer(port int) (*grpc.Server, net.Listener, grpc.DialOption, []byte) {
var returnedCertHash []byte
var s *grpc.Server
var ll net.Listener
var err error
Expand All @@ -533,10 +551,25 @@ func createGRPCLayer(port int) (*grpc.Server, net.Listener, grpc.DialOption) {

err = generateCertificates(keyFileName, certFileName)
if err == nil {
var creds credentials.TransportCredentials
creds, err = credentials.NewServerTLSFromFile(certFileName, keyFileName)
serverOpts = append(serverOpts, grpc.Creds(creds))
cert, err := tls.LoadX509KeyPair(certFileName, keyFileName)
if err != nil {
panic(err)
}

if len(cert.Certificate) == 0 {
panic(fmt.Errorf("Certificate chain is nil"))
}

returnedCertHash = certHashFromRawCert(cert.Certificate[0])

tlsConf := &tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequestClientCert,
InsecureSkipVerify: true,
}
serverOpts = append(serverOpts, grpc.Creds(credentials.NewTLS(tlsConf)))
ta := credentials.NewTLS(&tls.Config{
Certificates: []tls.Certificate{cert},
InsecureSkipVerify: true,
})
dialOpts = grpc.WithTransportCredentials(&authCreds{tlsCreds: ta})
Expand All @@ -551,5 +584,5 @@ func createGRPCLayer(port int) (*grpc.Server, net.Listener, grpc.DialOption) {
}

s = grpc.NewServer(serverOpts...)
return s, ll, dialOpts
return s, ll, dialOpts, returnedCertHash
}
16 changes: 11 additions & 5 deletions gossip/comm/comm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"crypto/tls"
"fmt"
"math/rand"
"os"
"sync"
"testing"
"time"
Expand Down Expand Up @@ -88,8 +89,13 @@ func newCommInstance(port int, sec api.MessageCryptoService) (Comm, error) {
}

func handshaker(endpoint string, comm Comm, t *testing.T, sigMutator func([]byte) []byte, pkiIDmutator func([]byte) []byte) <-chan ReceivedMessage {
err := generateCertificates("key.pem", "cert.pem")
defer os.Remove("cert.pem")
defer os.Remove("key.pem")
cert, err := tls.LoadX509KeyPair("cert.pem", "key.pem")
ta := credentials.NewTLS(&tls.Config{
InsecureSkipVerify: true,
Certificates: []tls.Certificate{cert},
})
acceptChan := comm.Accept(acceptAll)
conn, err := grpc.Dial("localhost:9611", grpc.WithTransportCredentials(&authCreds{tlsCreds: ta}), grpc.WithBlock(), grpc.WithTimeout(time.Second))
Expand All @@ -103,8 +109,8 @@ func handshaker(endpoint string, comm Comm, t *testing.T, sigMutator func([]byte
if err != nil {
return nil
}
clientTLSUnique := ExtractTLSUnique(stream.Context())
sig, err := naiveSec.Sign(clientTLSUnique)
clientCertHash := certHashFromRawCert(cert.Certificate[0])
sig, err := naiveSec.Sign(clientCertHash)
if sigMutator != nil {
sig = sigMutator(sig)
}
Expand All @@ -119,7 +125,7 @@ func handshaker(endpoint string, comm Comm, t *testing.T, sigMutator func([]byte
msg, err = stream.Recv()
assert.NoError(t, err, "%v", err)
if sigMutator == nil {
assert.Equal(t, clientTLSUnique, msg.GetConn().Sig)
assert.Equal(t, extractCertificateHashFromContext(stream.Context()), msg.GetConn().Sig)
}
assert.Equal(t, []byte("localhost:9611"), msg.GetConn().PkiID)
msg2Send := createGossipMsg()
Expand Down Expand Up @@ -152,10 +158,10 @@ func TestHandshake(t *testing.T) {
assert.Equal(t, 0, len(acceptChan))

// negative path, nothing should be read from the channel because the PKIid doesn't match the identity
mutateEndpoint := func(b []byte) []byte {
mutatePKIID := func(b []byte) []byte {
return []byte("localhost:9650")
}
acceptChan = handshaker("localhost:9613", comm, t, nil, mutateEndpoint)
acceptChan = handshaker("localhost:9613", comm, t, nil, mutatePKIID)
time.Sleep(time.Second)
assert.Equal(t, 0, len(acceptChan))
}
Expand Down
26 changes: 20 additions & 6 deletions gossip/comm/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"math/big"
"os"

"crypto/tls"
"net"
"os"
"time"

"golang.org/x/net/context"
Expand Down Expand Up @@ -71,8 +71,17 @@ func generateCertificates(privKeyFile string, certKeyFile string) error {
return err
}

// ExtractTLSUnique extracts the TLS-Unique from the stream
func ExtractTLSUnique(ctx context.Context) []byte {
func certHashFromRawCert(rawCert []byte) []byte {
if len(rawCert) == 0 {
return nil
}
hash := sha256.New()
hash.Write(rawCert)
return hash.Sum(nil)
}

// ExtractCertificateHash extracts the hash of the certificate from the stream
func extractCertificateHashFromContext(ctx context.Context) []byte {
pr, extracted := peer.FromContext(ctx)
if !extracted {
return nil
Expand All @@ -87,7 +96,12 @@ func ExtractTLSUnique(ctx context.Context) []byte {
if !isTLSConn {
return nil
}
return tlsInfo.State.TLSUnique
certs := tlsInfo.State.PeerCertificates
if len(certs) == 0 {
return nil
}
raw := certs[0].Raw
return certHashFromRawCert(raw)
}

type authCreds struct {
Expand Down

0 comments on commit a950854

Please sign in to comment.