Skip to content

Commit fb32914

Browse files
committed
[FAB-7237] Specify TLS client key pair for peer
Prior to this change, the peer used the TLS server key pair when making client TLS connections. In many cases, this will be fine, but in cases where the TLS server key pair is issued by a public CA (e.g. Verisign), this is an issue as now any client certificate issued by Versign will be valid. So this change simply adds an optional TLS client key pair to the peer config. Change-Id: I0195407ef0ecef58059e726a6c9eb6e473926cc8 Signed-off-by: Gari Singh <gari.r.singh@gmail.com>
1 parent dd99026 commit fb32914

File tree

6 files changed

+165
-14
lines changed

6 files changed

+165
-14
lines changed

core/comm/connection.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ type CASupport struct {
4444
// CredentialSupport type manages credentials used for gRPC client connections
4545
type CredentialSupport struct {
4646
*CASupport
47-
ClientCert tls.Certificate
47+
clientCert tls.Certificate
4848
}
4949

5050
// GetCredentialSupport returns the singleton CredentialSupport instance
@@ -109,6 +109,12 @@ func (cas *CASupport) GetClientRootCAs() (appRootCAs, ordererRootCAs [][]byte) {
109109
return appRootCAs, ordererRootCAs
110110
}
111111

112+
// SetClientCertificate sets the tls.Certificate to use for gRPC client
113+
// connections
114+
func (cs *CredentialSupport) SetClientCertificate(cert tls.Certificate) {
115+
cs.clientCert = cert
116+
}
117+
112118
// GetDeliverServiceCredentials returns GRPC transport credentials for given channel to be used by GRPC
113119
// clients which communicate with ordering service endpoints.
114120
// If the channel isn't found, error is returned.
@@ -118,7 +124,7 @@ func (cs *CredentialSupport) GetDeliverServiceCredentials(channelID string) (cre
118124

119125
var creds credentials.TransportCredentials
120126
tlsConfig := &tls.Config{
121-
Certificates: []tls.Certificate{cs.ClientCert},
127+
Certificates: []tls.Certificate{cs.clientCert},
122128
}
123129
certPool := x509.NewCertPool()
124130

@@ -151,7 +157,7 @@ func (cs *CredentialSupport) GetDeliverServiceCredentials(channelID string) (cre
151157
func (cs *CredentialSupport) GetPeerCredentials() credentials.TransportCredentials {
152158
var creds credentials.TransportCredentials
153159
tlsConfig := &tls.Config{
154-
Certificates: []tls.Certificate{cs.ClientCert},
160+
Certificates: []tls.Certificate{cs.clientCert},
155161
}
156162
certPool := x509.NewCertPool()
157163
// loop through the server root CAs

core/comm/connection_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,10 @@ func TestCredentialSupport(t *testing.T) {
195195
}
196196

197197
cs := GetCredentialSupport()
198-
cs.ClientCert = tls.Certificate{}
198+
cert := tls.Certificate{Certificate: [][]byte{}}
199+
cs.SetClientCertificate(cert)
200+
assert.Equal(t, cert, cs.clientCert)
201+
199202
cs.AppRootCAsByChain["channel1"] = [][]byte{rootCAs[0]}
200203
cs.AppRootCAsByChain["channel2"] = [][]byte{rootCAs[1]}
201204
cs.AppRootCAsByChain["channel3"] = [][]byte{rootCAs[2]}

core/peer/config.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ SPDX-License-Identifier: Apache-2.0
2020
package peer
2121

2222
import (
23+
"crypto/tls"
2324
"fmt"
2425
"io/ioutil"
2526
"net"
@@ -172,3 +173,59 @@ func GetServerConfig() (comm.ServerConfig, error) {
172173
}
173174
return serverConfig, nil
174175
}
176+
177+
// GetClientCertificate returns the TLS certificate to use for gRPC client
178+
// connections
179+
func GetClientCertificate() (tls.Certificate, error) {
180+
cert := tls.Certificate{}
181+
182+
keyPath := viper.GetString("peer.tls.clientKey.file")
183+
certPath := viper.GetString("peer.tls.clientCert.file")
184+
185+
if keyPath != "" || certPath != "" {
186+
// need both keyPath and certPath to be set
187+
if keyPath == "" || certPath == "" {
188+
return cert, errors.New("peer.tls.clientKey.file and " +
189+
"peer.tls.clientCert.file must both be set or must both be empty")
190+
}
191+
keyPath = config.GetPath("peer.tls.clientKey.file")
192+
certPath = config.GetPath("peer.tls.clientCert.file")
193+
194+
} else {
195+
// use the TLS server keypair
196+
keyPath = viper.GetString("peer.tls.key.file")
197+
certPath = viper.GetString("peer.tls.key.file")
198+
199+
if keyPath != "" || certPath != "" {
200+
// need both keyPath and certPath to be set
201+
if keyPath == "" || certPath == "" {
202+
return cert, errors.New("peer.tls.key.file and " +
203+
"peer.tls.cert.file must both be set or must both be empty")
204+
}
205+
keyPath = config.GetPath("peer.tls.key.file")
206+
certPath = config.GetPath("peer.tls.cert.file")
207+
} else {
208+
return cert, errors.New("must set either " +
209+
"[peer.tls.key.file and peer.tls.cert.file] or " +
210+
"[peer.tls.clientKey.file and peer.tls.clientCert.file]" +
211+
"when peer.tls.clientAuthEnabled is set to true")
212+
}
213+
}
214+
// get the keypair from the file system
215+
clientKey, err := ioutil.ReadFile(keyPath)
216+
if err != nil {
217+
return cert, errors.WithMessage(err,
218+
"error loading client TLS key")
219+
}
220+
clientCert, err := ioutil.ReadFile(certPath)
221+
if err != nil {
222+
return cert, errors.WithMessage(err,
223+
"error loading client TLS certificate")
224+
}
225+
cert, err = tls.X509KeyPair(clientCert, clientKey)
226+
if err != nil {
227+
return cert, errors.WithMessage(err,
228+
"error parsing client TLS key pair")
229+
}
230+
return cert, nil
231+
}

core/peer/config_test.go

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

88
import (
9+
"crypto/tls"
910
"fmt"
1011
"net"
1112
"path/filepath"
@@ -176,3 +177,75 @@ func TestGetServerConfig(t *testing.T) {
176177
viper.Set("peer.tls.clientAuthRequired", false)
177178

178179
}
180+
181+
func TestGetClientCertificate(t *testing.T) {
182+
viper.Set("peer.tls.key.file", "")
183+
viper.Set("peer.tls.cert.file", "")
184+
viper.Set("peer.tls.clientKey.file", "")
185+
viper.Set("peer.tls.clientCert.file", "")
186+
187+
// neither client nor server key pairs set - expect error
188+
_, err := GetClientCertificate()
189+
assert.Error(t, err)
190+
191+
viper.Set("peer.tls.key.file", "")
192+
viper.Set("peer.tls.cert.file",
193+
filepath.Join("testdata", "Org1-server1-cert.pem"))
194+
// missing server key file - expect error
195+
_, err = GetClientCertificate()
196+
assert.Error(t, err)
197+
198+
viper.Set("peer.tls.key.file",
199+
filepath.Join("testdata", "Org1-server1-key.pem"))
200+
viper.Set("peer.tls.cert.file", "")
201+
// missing server cert file - expect error
202+
_, err = GetClientCertificate()
203+
assert.Error(t, err)
204+
205+
// set server TLS settings to ensure we get the client TLS settings
206+
// when they are set properly
207+
viper.Set("peer.tls.key.file",
208+
filepath.Join("testdata", "Org1-server1-key.pem"))
209+
viper.Set("peer.tls.cert.file",
210+
filepath.Join("testdata", "Org1-server1-cert.pem"))
211+
212+
// peer.tls.clientCert.file not set - expect error
213+
viper.Set("peer.tls.clientKey.file",
214+
filepath.Join("testdata", "Org2-server1-key.pem"))
215+
_, err = GetClientCertificate()
216+
assert.Error(t, err)
217+
218+
// peer.tls.clientKey.file not set - expect error
219+
viper.Set("peer.tls.clientKey.file", "")
220+
viper.Set("peer.tls.clientCert.file",
221+
filepath.Join("testdata", "Org2-server1-cert.pem"))
222+
_, err = GetClientCertificate()
223+
assert.Error(t, err)
224+
225+
// client auth required and clientKey/clientCert set
226+
expected, err := tls.LoadX509KeyPair(
227+
filepath.Join("testdata", "Org2-server1-cert.pem"),
228+
filepath.Join("testdata", "Org2-server1-key.pem"))
229+
if err != nil {
230+
t.Fatalf("Failed to load test certificate (%s)", err)
231+
}
232+
viper.Set("peer.tls.clientKey.file",
233+
filepath.Join("testdata", "Org2-server1-key.pem"))
234+
cert, err := GetClientCertificate()
235+
assert.NoError(t, err)
236+
assert.Equal(t, expected, cert)
237+
238+
// client auth required and clientKey/clientCert not set - expect
239+
// client cert to be the server cert
240+
viper.Set("peer.tls.clientKey.file", "")
241+
viper.Set("peer.tls.clientCert.file", "")
242+
expected, err = tls.LoadX509KeyPair(
243+
filepath.Join("testdata", "Org1-server1-cert.pem"),
244+
filepath.Join("testdata", "Org1-server1-key.pem"))
245+
if err != nil {
246+
t.Fatalf("Failed to load test certificate (%s)", err)
247+
}
248+
cert, err = GetClientCertificate()
249+
assert.NoError(t, err)
250+
assert.Equal(t, expected, cert)
251+
}

peer/node/start.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,13 @@ func serve(args []string) error {
142142
// set up credential support
143143
cs := comm.GetCredentialSupport()
144144
cs.ServerRootCAs = serverConfig.SecOpts.ServerRootCAs
145+
146+
// set the cert to use if client auth is requested by remote endpoints
147+
clientCert, err := peer.GetClientCertificate()
148+
if err != nil {
149+
logger.Fatalf("Failed to set TLS client certficate (%s)", err)
150+
}
151+
comm.GetCredentialSupport().SetClientCertificate(clientCert)
145152
}
146153

147154
//TODO - do we need different SSL material for events ?
@@ -215,7 +222,6 @@ func serve(args []string) error {
215222
dialOpts = append(dialOpts, comm.ClientKeepaliveOptions(kaOpts)...)
216223

217224
if comm.TLSEnabled() {
218-
comm.GetCredentialSupport().ClientCert = peerServer.ServerCertificate()
219225
dialOpts = append(dialOpts, grpc.WithTransportCredentials(comm.GetCredentialSupport().GetPeerCredentials()))
220226
} else {
221227
dialOpts = append(dialOpts, grpc.WithInsecure())

sampleconfig/core.yaml

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -263,28 +263,34 @@ peer:
263263
# Note that peer-chaincode connections through chaincodeListenAddress is
264264
# not mutual TLS auth. See comments on chaincodeListenAddress for more info
265265
tls:
266-
# require server-side TLS
266+
# Require server-side TLS
267267
enabled: false
268-
# require client certificates / mutual TLS.
269-
# note that clients that are not configured to use a certificate will
268+
# Require client certificates / mutual TLS.
269+
# Note that clients that are not configured to use a certificate will
270270
# fail to connect to the peer.
271271
clientAuthRequired: false
272-
# X.509 certificate used for TLS server (and client if clientAuthEnabled
273-
# is set to true
272+
# X.509 certificate used for TLS server
274273
cert:
275274
file: tls/server.crt
276-
# private key used for TLS server (and client if clientAuthEnabled
275+
# Private key used for TLS server (and client if clientAuthEnabled
277276
# is set to true
278277
key:
279278
file: tls/server.key
280-
# trusted root certificate chain for tls.cert
279+
# Trusted root certificate chain for tls.cert
281280
rootcert:
282281
file: tls/ca.crt
283-
# set of root certificate authorities used to verify client certificates
282+
# Set of root certificate authorities used to verify client certificates
284283
clientRootCAs:
285284
files:
286285
- tls/ca.crt
287-
286+
# Private key used for TLS when making client connections. If
287+
# not set, peer.tls.key.file will be used instead
288+
clientKey:
289+
file:
290+
# X.509 certificate used for TLS when making client connections.
291+
# If not set, peer.tls.cert.file will be used instead
292+
clientCert:
293+
file:
288294
# The server name use to verify the hostname returned by TLS handshake
289295
serverhostoverride:
290296

0 commit comments

Comments
 (0)