Skip to content

Commit

Permalink
[FAB-6974] Decouple peer native TLS and shim
Browse files Browse the repository at this point in the history
Background: A common "known problem" is a misconfiguration of the peer
which ripples to the chaincode shim, and then prevents the shim
to connect to the peer, when TLS is enabled.

This is due to the fact that the TLS certificate SAN needs to match
to the configuration, but this is impossible in some cases,
and tricky in other cases.

Another aspect is that the chaincode container shouldn't actually
use organizational credentials to connect to the peer, since
the chaincode is logically an extension of the peer and managed
by it, and not by the organization.

This change set:
- Removes the code that bakes the peer's TLS CA certificate
  into the docker image.
- Instead, it adds to the uploaded files at chaincode container startup,
  the TLS CA certificate that is self-signed by the peer at startup.
- It also makes the chaincode service use a TLS certificate that is
  signed by that CA's private key, with the same SAN that the peer
  passes to the chaincode shim at its startup.
- Changes the unit tests of the core/chaincode/accesscontrol to reflect
  the change, and imitate a chaincode shim that is given the TLS CA cert
  and the mock chaincode service to use a TLS certificate signed
  by the CA.

Change-Id: Ife1e2a42b163b5e2372a127f118eccff0027780c
Signed-off-by: yacovm <yacovm@il.ibm.com>
  • Loading branch information
yacovm committed Dec 3, 2017
1 parent fb32914 commit e2583b7
Show file tree
Hide file tree
Showing 15 changed files with 154 additions and 211 deletions.
4 changes: 0 additions & 4 deletions bddtests/dc-base.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ services:
- CORE_PEER_TLS_CERT_FILE=${PEER0_CORE_PEER_TLS_CERT_FILE}
- CORE_PEER_TLS_KEY_FILE=${PEER0_CORE_PEER_TLS_KEY_FILE}
- CORE_PEER_TLS_ROOTCERT_FILE=${PEER0_CORE_PEER_TLS_ROOTCERT_FILE}
- CORE_PEER_TLS_SERVERHOSTOVERRIDE=${PEER0_CORE_PEER_TLS_SERVERHOSTOVERRIDE}
depends_on:
- orderer0
#ports:
Expand All @@ -49,7 +48,6 @@ services:
- CORE_PEER_TLS_CERT_FILE=${PEER1_CORE_PEER_TLS_CERT_FILE}
- CORE_PEER_TLS_KEY_FILE=${PEER1_CORE_PEER_TLS_KEY_FILE}
- CORE_PEER_TLS_ROOTCERT_FILE=${PEER1_CORE_PEER_TLS_ROOTCERT_FILE}
- CORE_PEER_TLS_SERVERHOSTOVERRIDE=${PEER1_CORE_PEER_TLS_SERVERHOSTOVERRIDE}
depends_on:
- orderer0
- peer0
Expand All @@ -66,7 +64,6 @@ services:
- CORE_PEER_TLS_CERT_FILE=${PEER2_CORE_PEER_TLS_CERT_FILE}
- CORE_PEER_TLS_KEY_FILE=${PEER2_CORE_PEER_TLS_KEY_FILE}
- CORE_PEER_TLS_ROOTCERT_FILE=${PEER2_CORE_PEER_TLS_ROOTCERT_FILE}
- CORE_PEER_TLS_SERVERHOSTOVERRIDE=${PEER2_CORE_PEER_TLS_SERVERHOSTOVERRIDE}
depends_on:
- orderer0
- peer0
Expand All @@ -83,7 +80,6 @@ services:
- CORE_PEER_TLS_CERT_FILE=${PEER3_CORE_PEER_TLS_CERT_FILE}
- CORE_PEER_TLS_KEY_FILE=${PEER3_CORE_PEER_TLS_KEY_FILE}
- CORE_PEER_TLS_ROOTCERT_FILE=${PEER3_CORE_PEER_TLS_ROOTCERT_FILE}
- CORE_PEER_TLS_SERVERHOSTOVERRIDE=${PEER3_CORE_PEER_TLS_SERVERHOSTOVERRIDE}
depends_on:
- orderer0
- peer0
2 changes: 1 addition & 1 deletion core/chaincode/accesscontrol/access.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ type authenticator struct {
// NewAuthenticator returns a new authenticator that would wrap the given chaincode service
func NewAuthenticator(srv pb.ChaincodeSupportServer, ca CA) Authenticator {
auth := &authenticator{
mapper: newCertMapper(ca.newCertKeyPair),
mapper: newCertMapper(ca.newClientCertKeyPair),
}
auth.ChaincodeSupportServer = newInterceptor(srv, auth.authenticate)
return auth
Expand Down
33 changes: 18 additions & 15 deletions core/chaincode/accesscontrol/access_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package accesscontrol

import (
"crypto/tls"
"crypto/x509"
"encoding/base64"
"fmt"
"net"
Expand Down Expand Up @@ -65,10 +66,10 @@ func (cs *ccSrv) stop() {
cs.l.Close()
}

func newCCServer(t *testing.T, port int, expectedCCname string, withTLS bool, clientCAcert []byte) *ccSrv {
func newCCServer(t *testing.T, port int, expectedCCname string, withTLS bool, ca CA) *ccSrv {
var s *grpc.Server
if withTLS {
s = createTLSService(t, clientCAcert)
s = createTLSService(t, ca, "localhost")
} else {
s = grpc.NewServer()
}
Expand All @@ -87,10 +88,12 @@ type ccClient struct {
stream pb.ChaincodeSupport_RegisterClient
}

func newClient(t *testing.T, port int, cert *tls.Certificate) (*ccClient, error) {
func newClient(t *testing.T, port int, cert *tls.Certificate, peerCACert []byte) (*ccClient, error) {
tlsCfg := &tls.Config{
InsecureSkipVerify: true,
RootCAs: x509.NewCertPool(),
}

tlsCfg.RootCAs.AppendCertsFromPEM(peerCACert)
if cert != nil {
tlsCfg.Certificates = []tls.Certificate{*cert}
}
Expand Down Expand Up @@ -160,23 +163,23 @@ func TestAccessControl(t *testing.T) {
}

ca, _ := NewCA()
srv := newCCServer(t, 7052, "example02", true, ca.CertBytes())
srv := newCCServer(t, 7052, "example02", true, ca)
auth := NewAuthenticator(srv, ca)
pb.RegisterChaincodeSupportServer(srv.grpcSrv, auth)
go srv.grpcSrv.Serve(srv.l)
defer srv.stop()

// Create an attacker without a TLS certificate
_, err = newClient(t, 7052, nil)
_, err = newClient(t, 7052, nil, ca.CertBytes())
assert.Error(t, err)
assert.Contains(t, err.Error(), "tls: bad certificate")

// Create an attacker with its own TLS certificate
maliciousCA, _ := NewCA()
keyPair, err := maliciousCA.newCertKeyPair()
cert, err := tls.X509KeyPair(keyPair.certBytes, keyPair.keyBytes)
keyPair, err := maliciousCA.newClientCertKeyPair()
cert, err := tls.X509KeyPair(keyPair.Cert, keyPair.Key)
assert.NoError(t, err)
_, err = newClient(t, 7052, &cert)
_, err = newClient(t, 7052, &cert, ca.CertBytes())
assert.Error(t, err)
assert.Contains(t, err.Error(), "tls: bad certificate")

Expand All @@ -189,7 +192,7 @@ func TestAccessControl(t *testing.T) {
assert.NoError(t, err)
cert, err = tls.X509KeyPair(certBytes, keyBytes)
assert.NoError(t, err)
mismatchedShim, err := newClient(t, 7052, &cert)
mismatchedShim, err := newClient(t, 7052, &cert, ca.CertBytes())
assert.NoError(t, err)
defer mismatchedShim.close()
mismatchedShim.sendMsg(registerMsg)
Expand All @@ -207,7 +210,7 @@ func TestAccessControl(t *testing.T) {
assert.NoError(t, err)
cert, err = tls.X509KeyPair(certBytes, keyBytes)
assert.NoError(t, err)
realCC, err := newClient(t, 7052, &cert)
realCC, err := newClient(t, 7052, &cert, ca.CertBytes())
assert.NoError(t, err)
defer realCC.close()
realCC.sendMsg(registerMsg)
Expand All @@ -231,7 +234,7 @@ func TestAccessControl(t *testing.T) {
assert.NoError(t, err)
cert, err = tls.X509KeyPair(certBytes, keyBytes)
assert.NoError(t, err)
confusedCC, err := newClient(t, 7052, &cert)
confusedCC, err := newClient(t, 7052, &cert, ca.CertBytes())
assert.NoError(t, err)
defer confusedCC.close()
confusedCC.sendMsg(putStateMsg)
Expand All @@ -250,7 +253,7 @@ func TestAccessControl(t *testing.T) {
assert.NoError(t, err)
cert, err = tls.X509KeyPair(certBytes, keyBytes)
assert.NoError(t, err)
malformedMessageCC, err := newClient(t, 7052, &cert)
malformedMessageCC, err := newClient(t, 7052, &cert, ca.CertBytes())
assert.NoError(t, err)
defer malformedMessageCC.close()
// Save old payload
Expand All @@ -276,7 +279,7 @@ func TestAccessControl(t *testing.T) {
assert.NoError(t, err)
cert, err = tls.X509KeyPair(certBytes, keyBytes)
assert.NoError(t, err)
lateCC, err := newClient(t, 7052, &cert)
lateCC, err := newClient(t, 7052, &cert, ca.CertBytes())
assert.NoError(t, err)
defer realCC.close()
time.Sleep(ttl + time.Second*2)
Expand All @@ -299,7 +302,7 @@ func TestAccessControlNoTLS(t *testing.T) {
}

ca, _ := NewCA()
s := newCCServer(t, 8052, "example02", false, ca.CertBytes())
s := newCCServer(t, 8052, "example02", false, ca)
auth := NewAuthenticator(s, ca)
pb.RegisterChaincodeSupportServer(s.grpcSrv, auth)
go s.grpcSrv.Serve(s.l)
Expand Down
42 changes: 34 additions & 8 deletions core/chaincode/accesscontrol/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ SPDX-License-Identifier: Apache-2.0

package accesscontrol

// CertKeyPair denotes a TLS certificate and corresponding key,
// both PEM encoded
type CertKeyPair struct {
// Cert is the certificate, PEM encoded
Cert []byte
// Key is the key corresponding to the certificate, PEM encoded
Key []byte
}

// CA defines a certificate authority that can generate
// certificates signed by it
type CA interface {
Expand All @@ -14,8 +23,14 @@ type CA interface {

// newCertKeyPair returns a certificate and private key pair and nil,
// or nil, error in case of failure
// The certificate is signed by the CA
newCertKeyPair() (*certKeyPair, error)
// The certificate is signed by the CA and is used for TLS client authentication
newClientCertKeyPair() (*certKeyPair, error)

// NewServerCertKeyPair returns a CertKeyPair and nil,
// with a given custom SAN.
// The certificate is signed by the CA.
// Returns nil, error in case of failure
NewServerCertKeyPair(host string) (*CertKeyPair, error)
}

type ca struct {
Expand All @@ -25,7 +40,7 @@ type ca struct {
func NewCA() (CA, error) {
c := &ca{}
var err error
c.caCert, err = newCertKeyPair(true, nil, nil)
c.caCert, err = newCertKeyPair(true, false, "", nil, nil)
if err != nil {
return nil, err
}
Expand All @@ -34,12 +49,23 @@ func NewCA() (CA, error) {

// CertBytes returns the certificate of the CA in PEM encoding
func (c *ca) CertBytes() []byte {
return c.caCert.certBytes
return c.caCert.Cert
}

// newCertKeyPair returns a certificate and private key pair and nil,
// newClientCertKeyPair returns a certificate and private key pair and nil,
// or nil, error in case of failure
// The certificate is signed by the CA
func (c *ca) newCertKeyPair() (*certKeyPair, error) {
return newCertKeyPair(false, c.caCert.Signer, c.caCert.cert)
// The certificate is signed by the CA and is used as a client TLS certificate
func (c *ca) newClientCertKeyPair() (*certKeyPair, error) {
return newCertKeyPair(false, false, "", c.caCert.Signer, c.caCert.cert)
}

// newServerCertKeyPair returns a certificate and private key pair and nil,
// or nil, error in case of failure
// The certificate is signed by the CA and is used as a server TLS certificate
func (c *ca) NewServerCertKeyPair(host string) (*CertKeyPair, error) {
keypair, err := newCertKeyPair(false, true, host, c.caCert.Signer, c.caCert.cert)
if err != nil {
return nil, err
}
return keypair.CertKeyPair, nil
}
26 changes: 13 additions & 13 deletions core/chaincode/accesscontrol/ca_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,16 @@ import (
"google.golang.org/grpc/credentials"
)

func createTLSService(t *testing.T, clientCAcert []byte) *grpc.Server {
ca, err := NewCA()
assert.NoError(t, err)
keyPair, err := ca.newCertKeyPair()
cert, err := tls.X509KeyPair(keyPair.certBytes, keyPair.keyBytes)
func createTLSService(t *testing.T, ca CA, host string) *grpc.Server {
keyPair, err := ca.NewServerCertKeyPair(host)
cert, err := tls.X509KeyPair(keyPair.Cert, keyPair.Key)
assert.NoError(t, err)
tlsConf := &tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: x509.NewCertPool(),
}
tlsConf.ClientCAs.AppendCertsFromPEM(clientCAcert)
tlsConf.ClientCAs.AppendCertsFromPEM(ca.CertBytes())
return grpc.NewServer(grpc.Creds(credentials.NewTLS(tlsConf)))
}

Expand All @@ -48,8 +46,9 @@ func TestTLSCA(t *testing.T) {
assert.NoError(t, err)
assert.NotNil(t, ca)

srv := createTLSService(t, ca.CertBytes())
l, err := net.Listen("tcp", fmt.Sprintf("%s:%d", "", randomPort))
endpoint := fmt.Sprintf("127.0.0.1:%d", randomPort)
srv := createTLSService(t, ca, "127.0.0.1")
l, err := net.Listen("tcp", endpoint)
assert.NoError(t, err)
go srv.Serve(l)
defer srv.Stop()
Expand All @@ -62,13 +61,14 @@ func TestTLSCA(t *testing.T) {
assert.NoError(t, err)
cert, err := tls.X509KeyPair(certBytes, keyBytes)
tlsCfg := &tls.Config{
InsecureSkipVerify: true,
Certificates: []tls.Certificate{cert},
RootCAs: x509.NewCertPool(),
Certificates: []tls.Certificate{cert},
}
tlsCfg.RootCAs.AppendCertsFromPEM(ca.CertBytes())
tlsOpts := grpc.WithTransportCredentials(credentials.NewTLS(tlsCfg))
ctx := context.Background()
ctx, _ = context.WithTimeout(ctx, time.Second)
conn, err := grpc.DialContext(ctx, fmt.Sprintf("localhost:%d", randomPort), tlsOpts, grpc.WithBlock())
conn, err := grpc.DialContext(ctx, fmt.Sprintf("127.0.0.1:%d", randomPort), tlsOpts, grpc.WithBlock())
if err != nil {
return err
}
Expand All @@ -78,14 +78,14 @@ func TestTLSCA(t *testing.T) {

// Good path - use a cert key pair generated from the CA
// that the TLS server started with
kp, err := ca.newCertKeyPair()
kp, err := ca.newClientCertKeyPair()
assert.NoError(t, err)
err = probeTLS(kp)
assert.NoError(t, err)

// Bad path - use a cert key pair generated from a foreign CA
foreignCA, _ := NewCA()
kp, err = foreignCA.newCertKeyPair()
kp, err = foreignCA.newClientCertKeyPair()
assert.NoError(t, err)
err = probeTLS(kp)
assert.Error(t, err)
Expand Down
35 changes: 25 additions & 10 deletions core/chaincode/accesscontrol/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,24 @@ import (
"encoding/base64"
"encoding/pem"
"math/big"
"net"
"time"
)

type KeyGenFunc func() (*certKeyPair, error)

type certKeyPair struct {
keyBytes []byte
certBytes []byte
*CertKeyPair
crypto.Signer
cert *x509.Certificate
}

func (p *certKeyPair) privKeyString() string {
return base64.StdEncoding.EncodeToString(p.keyBytes)
return base64.StdEncoding.EncodeToString(p.Key)
}

func (p *certKeyPair) pubKeyString() string {
return base64.StdEncoding.EncodeToString(p.certBytes)
return base64.StdEncoding.EncodeToString(p.Cert)
}

func newPrivKey() (*ecdsa.PrivateKey, []byte, error) {
Expand Down Expand Up @@ -60,7 +60,7 @@ func newCertTemplate() (x509.Certificate, error) {
}, nil
}

func newCertKeyPair(isCA bool, certSigner crypto.Signer, parent *x509.Certificate) (*certKeyPair, error) {
func newCertKeyPair(isCA bool, isServer bool, host string, certSigner crypto.Signer, parent *x509.Certificate) (*certKeyPair, error) {
privateKey, privBytes, err := newPrivKey()
if err != nil {
return nil, err
Expand All @@ -70,15 +70,28 @@ func newCertKeyPair(isCA bool, certSigner crypto.Signer, parent *x509.Certificat
if err != nil {
return nil, err
}

tenYearsFromNow := time.Now().Add(time.Hour * 24 * 365 * 10)
if isCA {
template.NotAfter = time.Now().Add(time.Hour * 24 * 365 * 10)
template.NotAfter = tenYearsFromNow
template.IsCA = true
template.KeyUsage |= x509.KeyUsageCertSign | x509.KeyUsageCRLSign
template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageAny}
template.BasicConstraintsValid = true
} else {
template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
}
if isServer {
template.NotAfter = tenYearsFromNow
template.ExtKeyUsage = append(template.ExtKeyUsage, x509.ExtKeyUsageServerAuth)
if ip := net.ParseIP(host); ip != nil {
logger.Debug("Classified", host, "as an IP address, adding it as an IP SAN")
template.IPAddresses = append(template.IPAddresses, ip)
} else {
logger.Debug("Classified", host, "as a hostname, adding it as a DNS SAN")
template.DNSNames = append(template.DNSNames, host)
}
}
// If no parent cert, it's a self signed cert
if parent == nil || certSigner == nil {
parent = &template
Expand All @@ -97,9 +110,11 @@ func newCertKeyPair(isCA bool, certSigner crypto.Signer, parent *x509.Certificat
}
privKey := encodePEM("EC PRIVATE KEY", privBytes)
return &certKeyPair{
Signer: privateKey,
keyBytes: privKey,
certBytes: pubKey,
cert: cert,
CertKeyPair: &CertKeyPair{
Key: privKey,
Cert: pubKey,
},
Signer: privateKey,
cert: cert,
}, nil
}
6 changes: 4 additions & 2 deletions core/chaincode/accesscontrol/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,10 @@ func certKeyPairFromString(privKey string, pubKey string) (*certKeyPair, error)
return nil, err
}
return &certKeyPair{
certBytes: pub,
keyBytes: priv,
CertKeyPair: &CertKeyPair{
Key: priv,
Cert: pub,
},
}, nil
}

Expand Down
Loading

0 comments on commit e2583b7

Please sign in to comment.