Skip to content

Commit 7134f6e

Browse files
committed
[FAB-11325] VerifyPeerCertificate in SecureOptions
This change set adds the tls.Config's VerifyPeerCertificate in order to add support for TLS layer certificate pinning. This is needed for client-side enforcement of TLS pinning with gRPC calls that aren't stream-based, but I added also server-side tests support to be symmetric. Change-Id: I5af7d51294a52c510cba81e4c92a9c7044a19b98 Signed-off-by: yacovm <yacovm@il.ibm.com>
1 parent 8a22a27 commit 7134f6e

File tree

5 files changed

+121
-1
lines changed

5 files changed

+121
-1
lines changed

core/comm/client.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ func (client *GRPCClient) parseSecureOptions(opts *SecureOptions) error {
7272
return nil
7373
}
7474
client.tlsConfig = &tls.Config{
75-
MinVersion: tls.VersionTLS12} // TLS 1.2 only
75+
VerifyPeerCertificate: opts.VerifyCertificate,
76+
MinVersion: tls.VersionTLS12} // TLS 1.2 only
7677
if len(opts.ServerRootCAs) > 0 {
7778
client.tlsConfig.RootCAs = x509.NewCertPool()
7879
for _, certBytes := range opts.ServerRootCAs {

core/comm/client_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ SPDX-License-Identifier: Apache-2.0
77
package comm_test
88

99
import (
10+
"bytes"
1011
"context"
1112
"crypto/tls"
1213
"crypto/x509"
@@ -281,6 +282,54 @@ func TestNewConnection(t *testing.T) {
281282
},
282283
success: true,
283284
},
285+
{
286+
name: "server TLS pinning success",
287+
serverPort: 8358,
288+
clientPort: 8358,
289+
config: comm.ClientConfig{
290+
SecOpts: &comm.SecureOptions{
291+
VerifyCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
292+
if bytes.Equal(rawCerts[0], testCerts.serverCert.Certificate[0]) {
293+
return nil
294+
}
295+
panic("mismatched certificate")
296+
},
297+
Certificate: testCerts.certPEM,
298+
Key: testCerts.keyPEM,
299+
UseTLS: true,
300+
RequireClientCert: true,
301+
ServerRootCAs: [][]byte{testCerts.caPEM}},
302+
Timeout: testTimeout},
303+
serverTLS: &tls.Config{
304+
Certificates: []tls.Certificate{testCerts.serverCert},
305+
ClientAuth: tls.RequireAndVerifyClientCert,
306+
ClientCAs: certPool,
307+
},
308+
success: true,
309+
},
310+
{
311+
name: "server TLS pinning failure",
312+
serverPort: 8359,
313+
clientPort: 8359,
314+
config: comm.ClientConfig{
315+
SecOpts: &comm.SecureOptions{
316+
VerifyCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
317+
return errors.New("TLS certificate mismatch")
318+
},
319+
Certificate: testCerts.certPEM,
320+
Key: testCerts.keyPEM,
321+
UseTLS: true,
322+
RequireClientCert: true,
323+
ServerRootCAs: [][]byte{testCerts.caPEM}},
324+
Timeout: testTimeout},
325+
serverTLS: &tls.Config{
326+
Certificates: []tls.Certificate{testCerts.serverCert},
327+
ClientAuth: tls.RequireAndVerifyClientCert,
328+
ClientCAs: certPool,
329+
},
330+
success: false,
331+
errorMsg: "context deadline exceeded",
332+
},
284333
}
285334

286335
for _, test := range tests {

core/comm/config.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package comm
88

99
import (
1010
"crypto/tls"
11+
"crypto/x509"
1112
"time"
1213

1314
"google.golang.org/grpc"
@@ -65,6 +66,10 @@ type ClientConfig struct {
6566
// SecureOptions defines the security parameters (e.g. TLS) for a
6667
// GRPCServer or GRPCClient instance
6768
type SecureOptions struct {
69+
// VerifyCertificate, if not nil, is called after normal
70+
// certificate verification by either a TLS client or server.
71+
// If it returns a non-nil error, the handshake is aborted and that error results.
72+
VerifyCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error
6873
// PEM-encoded X509 public key to be used for TLS communication
6974
Certificate []byte
7075
// PEM-encoded private key to be used for TLS communication

core/comm/server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ func NewGRPCServerFromListener(listener net.Listener, serverConfig ServerConfig)
8787
}
8888
//base server certificate
8989
grpcServer.tlsConfig = &tls.Config{
90+
VerifyPeerCertificate: secureConfig.VerifyCertificate,
9091
GetCertificate: getCert,
9192
SessionTicketsDisabled: true,
9293
CipherSuites: secureConfig.CipherSuites,

core/comm/server_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ SPDX-License-Identifier: Apache-2.0
77
package comm_test
88

99
import (
10+
"bytes"
1011
"crypto/tls"
1112
"crypto/x509"
1213
"errors"
@@ -20,6 +21,7 @@ import (
2021
"testing"
2122
"time"
2223

24+
"github.com/hyperledger/fabric/common/crypto/tlsgen"
2325
"github.com/hyperledger/fabric/core/comm"
2426
testpb "github.com/hyperledger/fabric/core/comm/testdata/grpc"
2527
"github.com/stretchr/testify/assert"
@@ -656,6 +658,68 @@ func TestNewSecureGRPCServer(t *testing.T) {
656658
}
657659
}
658660

661+
func TestVerifyCertificateCallback(t *testing.T) {
662+
t.Parallel()
663+
664+
ca, err := tlsgen.NewCA()
665+
assert.NoError(t, err)
666+
667+
authorizedClientKeyPair, err := ca.NewClientCertKeyPair()
668+
assert.NoError(t, err)
669+
670+
notAuthorizedClientKeyPair, err := ca.NewClientCertKeyPair()
671+
assert.NoError(t, err)
672+
673+
serverKeyPair, err := ca.NewServerCertKeyPair("127.0.0.1")
674+
assert.NoError(t, err)
675+
676+
verifyFunc := func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
677+
if bytes.Equal(rawCerts[0], authorizedClientKeyPair.TLSCert.Raw) {
678+
return nil
679+
}
680+
return errors.New("certificate mismatch")
681+
}
682+
683+
probeTLS := func(endpoint string, clientKeyPair *tlsgen.CertKeyPair) error {
684+
cert, err := tls.X509KeyPair(clientKeyPair.Cert, clientKeyPair.Key)
685+
tlsCfg := &tls.Config{
686+
Certificates: []tls.Certificate{cert},
687+
RootCAs: x509.NewCertPool(),
688+
}
689+
tlsCfg.RootCAs.AppendCertsFromPEM(ca.CertBytes())
690+
691+
conn, err := tls.Dial("tcp", endpoint, tlsCfg)
692+
if err != nil {
693+
return err
694+
}
695+
conn.Close()
696+
return nil
697+
}
698+
699+
gRPCServer, err := comm.NewGRPCServer("127.0.0.1:", comm.ServerConfig{
700+
SecOpts: &comm.SecureOptions{
701+
ClientRootCAs: [][]byte{ca.CertBytes()},
702+
Key: serverKeyPair.Key,
703+
Certificate: serverKeyPair.Cert,
704+
UseTLS: true,
705+
VerifyCertificate: verifyFunc,
706+
},
707+
})
708+
go gRPCServer.Start()
709+
defer gRPCServer.Stop()
710+
711+
t.Run("Success path", func(t *testing.T) {
712+
err = probeTLS(gRPCServer.Address(), authorizedClientKeyPair)
713+
assert.NoError(t, err)
714+
})
715+
716+
t.Run("Failure path", func(t *testing.T) {
717+
err = probeTLS(gRPCServer.Address(), notAuthorizedClientKeyPair)
718+
assert.EqualError(t, err, "remote error: tls: bad certificate")
719+
})
720+
721+
}
722+
659723
func TestNewSecureGRPCServerFromListener(t *testing.T) {
660724

661725
t.Parallel()

0 commit comments

Comments
 (0)