Skip to content

Commit 0754678

Browse files
committed
[FAB-7028] Dynamic TLS cert update
This change set adds an ability to the secureServer to dynamically update its TLS certificate. Change-Id: I1152ce65f203092ddd170ff494205eefeafc2e75 Signed-off-by: yacovm <yacovm@il.ibm.com>
1 parent 792e4fe commit 0754678

File tree

7 files changed

+143
-9
lines changed

7 files changed

+143
-9
lines changed

core/comm/server.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"fmt"
1414
"net"
1515
"sync"
16+
"sync/atomic"
1617

1718
"google.golang.org/grpc"
1819
)
@@ -46,6 +47,8 @@ type GRPCServer interface {
4647
// SetClientRootCAs sets the list of authorities used to verify client
4748
// certificates based on a list of PEM-encoded X509 certificate authorities
4849
SetClientRootCAs(clientRoots [][]byte) error
50+
// SetServerCertificate assigns the current TLS certificate to be the peer's server certificate
51+
SetServerCertificate(tls.Certificate)
4952
}
5053

5154
type grpcServerImpl struct {
@@ -56,7 +59,8 @@ type grpcServerImpl struct {
5659
// GRPC server
5760
server *grpc.Server
5861
// Certificate presented by the server for TLS communication
59-
serverCertificate tls.Certificate
62+
// stored as an atomic reference
63+
serverCertificate atomic.Value
6064
// Key used by the server for TLS communication
6165
serverKeyPEM []byte
6266
// List of certificate authorities to optionally pass to the client during
@@ -112,14 +116,17 @@ func NewGRPCServerFromListener(listener net.Listener, serverConfig ServerConfig)
112116
if err != nil {
113117
return nil, err
114118
}
115-
grpcServer.serverCertificate = cert
119+
grpcServer.serverCertificate.Store(cert)
116120

117121
//set up our TLS config
118122

123+
getCert := func(_ *tls.ClientHelloInfo) (*tls.Certificate, error) {
124+
cert := grpcServer.serverCertificate.Load().(tls.Certificate)
125+
return &cert, nil
126+
}
119127
//base server certificate
120-
certificates := []tls.Certificate{grpcServer.serverCertificate}
121128
grpcServer.tlsConfig = &tls.Config{
122-
Certificates: certificates,
129+
GetCertificate: getCert,
123130
SessionTicketsDisabled: true,
124131
}
125132
grpcServer.tlsConfig.ClientAuth = tls.RequestClientCert
@@ -160,6 +167,11 @@ func NewGRPCServerFromListener(listener net.Listener, serverConfig ServerConfig)
160167
return grpcServer, nil
161168
}
162169

170+
// SetServerCertificate assigns the current TLS certificate to be the peer's server certificate
171+
func (gServer *grpcServerImpl) SetServerCertificate(cert tls.Certificate) {
172+
gServer.serverCertificate.Store(cert)
173+
}
174+
163175
// Address returns the listen address for this GRPCServer instance
164176
func (gServer *grpcServerImpl) Address() string {
165177
return gServer.address
@@ -177,7 +189,7 @@ func (gServer *grpcServerImpl) Server() *grpc.Server {
177189

178190
// ServerCertificate returns the tls.Certificate used by the grpc.Server
179191
func (gServer *grpcServerImpl) ServerCertificate() tls.Certificate {
180-
return gServer.serverCertificate
192+
return gServer.serverCertificate.Load().(tls.Certificate)
181193
}
182194

183195
// TLSEnabled is a flag indicating whether or not TLS is enabled for the

core/comm/server_test.go

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,13 @@ import (
2020
"testing"
2121
"time"
2222

23+
"github.com/hyperledger/fabric/core/comm"
24+
testpb "github.com/hyperledger/fabric/core/comm/testdata/grpc"
2325
"github.com/stretchr/testify/assert"
24-
2526
"golang.org/x/net/context"
2627
"google.golang.org/grpc"
2728
"google.golang.org/grpc/credentials"
2829
"google.golang.org/grpc/transport"
29-
30-
"github.com/hyperledger/fabric/core/comm"
31-
testpb "github.com/hyperledger/fabric/core/comm/testdata/grpc"
3230
)
3331

3432
//Embedded certificates for testing
@@ -1425,3 +1423,70 @@ func TestKeepaliveClientResponse(t *testing.T) {
14251423
_, err = clientTransport.NewStream(context.Background(), &transport.CallHdr{})
14261424
assert.NoError(t, err, "Unexpected error creating stream")
14271425
}
1426+
1427+
func TestUpdateTLSCert(t *testing.T) {
1428+
readFile := func(path string) []byte {
1429+
fName := filepath.Join("testdata", "dynamic_cert_update", path)
1430+
data, err := ioutil.ReadFile(fName)
1431+
if err != nil {
1432+
panic(fmt.Errorf("Failed reading %s: %v", fName, err))
1433+
}
1434+
return data
1435+
}
1436+
loadBytes := func(prefix string) (key, cert, caCert []byte) {
1437+
cert = readFile(filepath.Join(prefix, "server.crt"))
1438+
key = readFile(filepath.Join(prefix, "server.key"))
1439+
caCert = readFile(filepath.Join("ca.crt"))
1440+
return
1441+
}
1442+
1443+
key, cert, caCert := loadBytes("notlocalhost")
1444+
1445+
cfg := comm.ServerConfig{
1446+
SecOpts: &comm.SecureOptions{
1447+
UseTLS: true,
1448+
ServerKey: key,
1449+
ServerCertificate: cert,
1450+
},
1451+
}
1452+
srv, err := comm.NewGRPCServer("localhost:8333", cfg)
1453+
assert.NoError(t, err)
1454+
testpb.RegisterTestServiceServer(srv.Server(), &testServiceServer{})
1455+
go srv.Start()
1456+
defer srv.Stop()
1457+
1458+
certPool := x509.NewCertPool()
1459+
certPool.AppendCertsFromPEM(caCert)
1460+
1461+
probeServer := func() error {
1462+
_, err = invokeEmptyCall("localhost:8333",
1463+
[]grpc.DialOption{grpc.WithTransportCredentials(
1464+
credentials.NewTLS(&tls.Config{
1465+
RootCAs: certPool}))})
1466+
return err
1467+
}
1468+
1469+
// bootstrap TLS certificate has a SAN of "notlocalhost" so it should fail
1470+
err = probeServer()
1471+
assert.Error(t, err)
1472+
assert.Contains(t, err.Error(), "certificate is valid for notlocalhost.org1.example.com, notlocalhost, not localhost")
1473+
1474+
// new TLS certificate has a SAN of "localhost" so it should succeed
1475+
certPath := filepath.Join("testdata", "dynamic_cert_update", "localhost", "server.crt")
1476+
keyPath := filepath.Join("testdata", "dynamic_cert_update", "localhost", "server.key")
1477+
tlsCert, err := tls.LoadX509KeyPair(certPath, keyPath)
1478+
assert.NoError(t, err)
1479+
srv.SetServerCertificate(tlsCert)
1480+
err = probeServer()
1481+
assert.NoError(t, err)
1482+
1483+
// revert back to the old certificate, should fail.
1484+
certPath = filepath.Join("testdata", "dynamic_cert_update", "notlocalhost", "server.crt")
1485+
keyPath = filepath.Join("testdata", "dynamic_cert_update", "notlocalhost", "server.key")
1486+
tlsCert, err = tls.LoadX509KeyPair(certPath, keyPath)
1487+
assert.NoError(t, err)
1488+
srv.SetServerCertificate(tlsCert)
1489+
err = probeServer()
1490+
assert.Error(t, err)
1491+
assert.Contains(t, err.Error(), "certificate is valid for notlocalhost.org1.example.com, notlocalhost, not localhost")
1492+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIICSTCCAe+gAwIBAgIQZMqAAhpj/lLHsJeIp1nJ7zAKBggqhkjOPQQDAjB2MQsw
3+
CQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZy
4+
YW5jaXNjbzEZMBcGA1UEChMQb3JnMS5leGFtcGxlLmNvbTEfMB0GA1UEAxMWdGxz
5+
Y2Eub3JnMS5leGFtcGxlLmNvbTAeFw0xNzExMTcyMjM4NTZaFw0yNzExMTUyMjM4
6+
NTZaMHYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH
7+
Ew1TYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBvcmcxLmV4YW1wbGUuY29tMR8wHQYD
8+
VQQDExZ0bHNjYS5vcmcxLmV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D
9+
AQcDQgAEMDe9E6+fydjWG40IHBnS1sZh4Mpw4G1KCWd4plTPZb0qJ7YaLkARx2dm
10+
d65FGh7dhJGUBQTNWa3/cLVB28tQVaNfMF0wDgYDVR0PAQH/BAQDAgGmMA8GA1Ud
11+
JQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zApBgNVHQ4EIgQgLzJgBSiEH1GF
12+
M+2iuJA92mp7j0SPqxmRmYyfbI+1+3wwCgYIKoZIzj0EAwIDSAAwRQIhAIEXLz9u
13+
XpAt1nXTuEVYAJYipi6TYtSnsOB/teMxZ887AiAs5IU1lWKYk4/RXrU9NgNdrUs+
14+
hLygondsbVWt6bKZzg==
15+
-----END CERTIFICATE-----
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIICdDCCAhqgAwIBAgIRAK5HIE/tumHtKRObBKPvnYQwCgYIKoZIzj0EAwIwdjEL
3+
MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG
4+
cmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHzAdBgNVBAMTFnRs
5+
c2NhLm9yZzEuZXhhbXBsZS5jb20wHhcNMTcxMTE3MjIzODU2WhcNMjcxMTE1MjIz
6+
ODU2WjBfMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UE
7+
BxMNU2FuIEZyYW5jaXNjbzEjMCEGA1UEAxMabG9jYWxob3N0Lm9yZzEuZXhhbXBs
8+
ZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASyPS7GHgSfNsvB8X5d8WT2
9+
91Z1AG+Ie5OpkUtI4Cmqq4lTUz+ba1f22EftkP8AsvO3NV6EBPsTNnUgqwORk4Lu
10+
o4GfMIGcMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB
11+
BQUHAwIwDAYDVR0TAQH/BAIwADArBgNVHSMEJDAigCAvMmAFKIQfUYUz7aK4kD3a
12+
anuPRI+rGZGZjJ9sj7X7fDAwBgNVHREEKTAnghpsb2NhbGhvc3Qub3JnMS5leGFt
13+
cGxlLmNvbYIJbG9jYWxob3N0MAoGCCqGSM49BAMCA0gAMEUCIQDAYC/+I9f3Z8rk
14+
bUmmZojIcf+VKtt2r/Ws2gurw/OxSgIgKevKSlauM5DlLDvaJVgsbQhxmoUL/oyt
15+
3bQu7kpCZ0k=
16+
-----END CERTIFICATE-----
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg4fKabn/wDrH9CNFt
3+
p41HuWqRZEalrLk0mwkVt42dCWahRANCAASyPS7GHgSfNsvB8X5d8WT291Z1AG+I
4+
e5OpkUtI4Cmqq4lTUz+ba1f22EftkP8AsvO3NV6EBPsTNnUgqwORk4Lu
5+
-----END PRIVATE KEY-----
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIICfTCCAiOgAwIBAgIRAPSubu0GrmfLZ0r9H0rOBdcwCgYIKoZIzj0EAwIwdjEL
3+
MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG
4+
cmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHzAdBgNVBAMTFnRs
5+
c2NhLm9yZzEuZXhhbXBsZS5jb20wHhcNMTcxMTE3MjIzODU2WhcNMjcxMTE1MjIz
6+
ODU2WjBiMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UE
7+
BxMNU2FuIEZyYW5jaXNjbzEmMCQGA1UEAxMdbm90bG9jYWxob3N0Lm9yZzEuZXhh
8+
bXBsZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4mepCqRSHMmEa7obW
9+
2BUBDXudRuuL5q2fKH62+6u22PChgavqTxs0AH7To6F6+vnGfq6RcvNe5fjQxphX
10+
rbgyo4GlMIGiMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYI
11+
KwYBBQUHAwIwDAYDVR0TAQH/BAIwADArBgNVHSMEJDAigCAvMmAFKIQfUYUz7aK4
12+
kD3aanuPRI+rGZGZjJ9sj7X7fDA2BgNVHREELzAtgh1ub3Rsb2NhbGhvc3Qub3Jn
13+
MS5leGFtcGxlLmNvbYIMbm90bG9jYWxob3N0MAoGCCqGSM49BAMCA0gAMEUCIQCw
14+
3ysg3FcPyIlMSlAhWcvNcf9ANpntSC7iRhexEdbsEgIgOpYSvBhLZTIo5OcmHHhy
15+
Uj5s3rI2qK45zuylQ2WSRG0=
16+
-----END CERTIFICATE-----
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgFec9IzGbSVizQbd3
3+
LjWHk9o3yACtfn4SnAdqMPWSgJOhRANCAAS4mepCqRSHMmEa7obW2BUBDXudRuuL
4+
5q2fKH62+6u22PChgavqTxs0AH7To6F6+vnGfq6RcvNe5fjQxphXrbgy
5+
-----END PRIVATE KEY-----

0 commit comments

Comments
 (0)