Skip to content

Commit

Permalink
[FAB-4093] Fix the TLS client using BCCSP
Browse files Browse the repository at this point in the history
The TLS client fails when client authentication is enabled and
the key was generated by BCCSP.  This is because the key file
name can not be found since its name is the hash of the public
key.  This change set fixes this for the TLS client in the same
way that https://gerrit.hyperledger.org/r/#/c/9235 fixed this
for the TLS server.  It reuses the util.LoadX509KeyPair function
for the client as is already used for the server.

See TestTLSClientAuth in lib/client_whitebox_test.go for the test
case.

Change-Id: I61432042e5fde3e300e855cd60499862a90cd715
Signed-off-by: Keith Smith <bksmith@us.ibm.com>
  • Loading branch information
Keith Smith committed Jun 5, 2017
1 parent 4435ed8 commit f963ce8
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 27 deletions.
4 changes: 2 additions & 2 deletions lib/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,7 @@ func (ca *CA) initDB() error {
return err
}
case "mysql":
ca.db, exists, err = dbutil.NewUserRegistryMySQL(db.Datasource, &db.TLS)
ca.db, exists, err = dbutil.NewUserRegistryMySQL(db.Datasource, &db.TLS, ca.csp)
if err != nil {
return err
}
Expand Down Expand Up @@ -486,7 +486,7 @@ func (ca *CA) initUserRegistry() error {

if ldapCfg.Enabled {
// Use LDAP for the user registry
ca.registry, err = ldap.NewClient(ldapCfg)
ca.registry, err = ldap.NewClient(ldapCfg, ca.server.csp)
log.Debugf("Initialized LDAP identity registry; err=%s", err)
return err
}
Expand Down
2 changes: 1 addition & 1 deletion lib/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ func (c *Client) SendReq(req *http.Request, result interface{}) (err error) {
return err
}

tlsConfig, err2 := tls.GetClientTLSConfig(&c.Config.TLS)
tlsConfig, err2 := tls.GetClientTLSConfig(&c.Config.TLS, c.csp)
if err2 != nil {
return fmt.Errorf("Failed to get client TLS config: %s", err2)
}
Expand Down
108 changes: 104 additions & 4 deletions lib/client_whitebox_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ import (
)

const (
whitePort = 7058
user = "admin"
pass = "adminpw"
serversDir = "testservers"
whitePort = 7058
user = "admin"
pass = "adminpw"
serversDir = "testservers"
testTLSClientAuthDir = "testTLSClientAuthDir"
)

var clientConfig = path.Join(testdataDir, "client-config.json")
Expand All @@ -52,6 +53,105 @@ func TestClient1(t *testing.T) {
os.RemoveAll(serversDir)
}

// TestTLS performs 3 main steps:
// 1) Test over HTTP to get an standard ecert
// 2) Test over HTTPS with client auth disabled
// 3) Test over HTTPS with client auth enabled, using standard ecert from #1
func TestTLSClientAuth(t *testing.T) {
os.RemoveAll(testTLSClientAuthDir)
defer os.RemoveAll(testTLSClientAuthDir)
//
// 1) Test over HTTP to get a standard ecert
//
// Start server
server := getServer(whitePort, path.Join(testTLSClientAuthDir, "server"), "", 1, t)
if server == nil {
return
}
server.CA.Config.CSR.CN = "localhost"
err := server.Start()
if err != nil {
t.Fatalf("Failed to start server: %s", err)
}
defer server.Stop()
// Enroll over HTTP
client := &Client{
Config: &ClientConfig{URL: fmt.Sprintf("http://localhost:%d", whitePort)},
HomeDir: path.Join(testTLSClientAuthDir, "client"),
}
eresp, err := client.Enroll(&api.EnrollmentRequest{Name: user, Secret: pass})
if err != nil {
t.Fatalf("Failed to enroll admin: %s", err)
}
id := eresp.Identity
// Stop server
err = server.Stop()
if err != nil {
t.Fatalf("Failed to stop server: %s", err)
}

//
// 2) Test over HTTPS with client auth disabled
//
// Start server
server.Config.TLS.Enabled = true
server.Config.TLS.CertFile = "ca-cert.pem"
err = server.Start()
if err != nil {
t.Fatalf("Failed to start server with HTTPS: %s", err)
}
// Try to reenroll over HTTP and it should fail because server is listening on HTTPS
_, err = id.Reenroll(&api.ReenrollmentRequest{})
if err == nil {
t.Fatal("Client HTTP should have failed to reenroll with server HTTPS")
}
// Reenroll over HTTPS
client.Config.URL = fmt.Sprintf("https://localhost:%d", whitePort)
client.Config.TLS.Enabled = true
client.Config.TLS.CertFiles = []string{"../server/ca-cert.pem"}
resp, err := id.Reenroll(&api.ReenrollmentRequest{})
if err != nil {
t.Fatalf("Failed to reenroll over HTTPS: %s", err)
}
id = resp.Identity
// Store identity persistently
err = id.Store()
if err != nil {
t.Fatalf("Failed to store identity: %s", err)
}
// Stop server
err = server.Stop()
if err != nil {
t.Fatalf("Failed to stop server: %s", err)
}

//
// 3) Test over HTTPS with client auth enabled
//
server.Config.TLS.ClientAuth.Type = "RequireAndVerifyClientCert"
server.Config.TLS.ClientAuth.CertFiles = []string{"ca-cert.pem"}
err = server.Start()
if err != nil {
t.Fatalf("Failed to start server with HTTPS and client auth: %s", err)
}
// Try to reenroll and it should fail because client has no client cert
_, err = id.Reenroll(&api.ReenrollmentRequest{})
if err == nil {
t.Fatal("Client reenroll without client cert should have failed")
}
// Reenroll over HTTPS with client auth
client.Config.TLS.Client.CertFile = path.Join("msp", "signcerts", "cert.pem")
_, err = id.Reenroll(&api.ReenrollmentRequest{})
if err != nil {
t.Fatalf("Client reenroll with client auth failed: %s", err)
}
// Stop server
err = server.Stop()
if err != nil {
t.Fatalf("Failed to stop server: %s", err)
}
}

func testInvalidAuthEnrollment(t *testing.T) {
c := getTestClient(whitePort)
err := c.Init()
Expand Down
7 changes: 4 additions & 3 deletions lib/dbutil/dbutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/cloudflare/cfssl/log"
"github.com/go-sql-driver/mysql"
"github.com/hyperledger/fabric-ca/lib/tls"
"github.com/hyperledger/fabric/bccsp"
"github.com/jmoiron/sqlx"
)

Expand Down Expand Up @@ -200,8 +201,8 @@ func createPostgresDBTables(datasource string, dbName string, db *sqlx.DB) error
return nil
}

// NewUserRegistryMySQL opens a connecton to a MySQL database
func NewUserRegistryMySQL(datasource string, clientTLSConfig *tls.ClientTLSConfig) (*sqlx.DB, bool, error) {
// NewUserRegistryMySQL opens a connecton to a postgres database
func NewUserRegistryMySQL(datasource string, clientTLSConfig *tls.ClientTLSConfig, csp bccsp.BCCSP) (*sqlx.DB, bool, error) {
log.Debugf("Using MySQL database, connecting to database...")

var exists bool
Expand All @@ -212,7 +213,7 @@ func NewUserRegistryMySQL(datasource string, clientTLSConfig *tls.ClientTLSConfi
connStr := re.ReplaceAllString(datasource, "/")

if clientTLSConfig.Enabled {
tlsConfig, err := tls.GetClientTLSConfig(clientTLSConfig)
tlsConfig, err := tls.GetClientTLSConfig(clientTLSConfig, csp)
if err != nil {
return nil, false, fmt.Errorf("Failed to get client TLS for MySQL: %s", err)
}
Expand Down
7 changes: 5 additions & 2 deletions lib/ldap/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/cloudflare/cfssl/log"
"github.com/hyperledger/fabric-ca/lib/spi"
ctls "github.com/hyperledger/fabric-ca/lib/tls"
"github.com/hyperledger/fabric/bccsp"
ldap "gopkg.in/ldap.v2"
)

Expand All @@ -45,7 +46,7 @@ type Config struct {
}

// NewClient creates an LDAP client
func NewClient(cfg *Config) (*Client, error) {
func NewClient(cfg *Config, csp bccsp.BCCSP) (*Client, error) {
log.Debugf("Creating new LDAP client for %+v", cfg)
if cfg == nil {
return nil, errors.New("LDAP configuration is nil")
Expand Down Expand Up @@ -95,6 +96,7 @@ func NewClient(cfg *Config) (*Client, error) {
c.UserFilter = cfgVal(cfg.UserFilter, "(uid=%s)")
c.GroupFilter = cfgVal(cfg.GroupFilter, "(memberUid=%s)")
c.TLS = &cfg.TLS
c.CSP = csp
log.Debug("LDAP client was successfully created")
return c, nil
}
Expand All @@ -118,6 +120,7 @@ type Client struct {
GroupFilter string // e.g. "(memberUid=%s)"
AdminConn *ldap.Conn
TLS *ctls.ClientTLSConfig
CSP bccsp.BCCSP
}

// GetUser returns a user object for username and attribute values
Expand Down Expand Up @@ -241,7 +244,7 @@ func (lc *Client) newConnection() (conn *ldap.Conn, err error) {
}
} else {
log.Debug("Connecting to LDAP server over TLS")
tlsConfig, err2 := ctls.GetClientTLSConfig(lc.TLS)
tlsConfig, err2 := ctls.GetClientTLSConfig(lc.TLS, lc.CSP)
if err2 != nil {
return nil, fmt.Errorf("Failed to get client TLS config: %s", err2)
}
Expand Down
9 changes: 5 additions & 4 deletions lib/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1065,7 +1065,8 @@ func TestNewUserRegistryMySQL(t *testing.T) {
tlsConfig := &tls.ClientTLSConfig{
Enabled: true,
}
_, _, err := dbutil.NewUserRegistryMySQL(datasource, tlsConfig)
csp := util.GetDefaultBCCSP()
_, _, err := dbutil.NewUserRegistryMySQL(datasource, tlsConfig, csp)
assert.Error(t, err)
assert.Contains(t, err.Error(), "No TLS certificate files were provided")

Expand All @@ -1074,7 +1075,7 @@ func TestNewUserRegistryMySQL(t *testing.T) {
Enabled: true,
CertFiles: []string{"doesnotexit.pem"},
}
_, _, err = dbutil.NewUserRegistryMySQL(datasource, tlsConfig)
_, _, err = dbutil.NewUserRegistryMySQL(datasource, tlsConfig, csp)
assert.Error(t, err)
assert.Contains(t, err.Error(), "no such file or directory")

Expand All @@ -1083,7 +1084,7 @@ func TestNewUserRegistryMySQL(t *testing.T) {
Enabled: true,
CertFiles: []string{"../testdata/empty.json"},
}
_, _, err = dbutil.NewUserRegistryMySQL(datasource, tlsConfig)
_, _, err = dbutil.NewUserRegistryMySQL(datasource, tlsConfig, csp)
assert.Error(t, err)
assert.Contains(t, err.Error(), "Failed to process certificate from file")

Expand All @@ -1097,7 +1098,7 @@ func TestNewUserRegistryMySQL(t *testing.T) {
Enabled: true,
CertFiles: []string{"../testdata/root.pem"},
}
_, _, err = dbutil.NewUserRegistryMySQL(datasource, tlsConfig)
_, _, err = dbutil.NewUserRegistryMySQL(datasource, tlsConfig, csp)
assert.Error(t, err)
assert.Contains(t, err.Error(), "permission denied")

Expand Down
14 changes: 10 additions & 4 deletions lib/tls/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import (

"github.com/cloudflare/cfssl/log"
"github.com/hyperledger/fabric-ca/util"
"github.com/hyperledger/fabric/bccsp"
"github.com/hyperledger/fabric/bccsp/factory"
)

// ServerTLSConfig defines key material for a TLS server
Expand Down Expand Up @@ -56,25 +58,29 @@ type KeyCertFiles struct {
}

// GetClientTLSConfig creates a tls.Config object from certs and roots
func GetClientTLSConfig(cfg *ClientTLSConfig) (*tls.Config, error) {
func GetClientTLSConfig(cfg *ClientTLSConfig, csp bccsp.BCCSP) (*tls.Config, error) {
var certs []tls.Certificate

if csp == nil {
csp = factory.GetDefault()
}

log.Debugf("CA Files: %+v\n", cfg.CertFiles)
log.Debugf("Client Cert File: %s\n", cfg.Client.CertFile)
log.Debugf("Client Key File: %s\n", cfg.Client.KeyFile)

if cfg.Client.CertFile != "" && cfg.Client.KeyFile != "" {
if cfg.Client.CertFile != "" {
err := checkCertDates(cfg.Client.CertFile)
if err != nil {
return nil, err
}

clientCert, err := tls.LoadX509KeyPair(cfg.Client.CertFile, cfg.Client.KeyFile)
clientCert, err := util.LoadX509KeyPair(cfg.Client.CertFile, cfg.Client.KeyFile, csp)
if err != nil {
return nil, err
}

certs = append(certs, clientCert)
certs = append(certs, *clientCert)
} else {
log.Debug("Client TLS certificate and/or key file not provided")
}
Expand Down
12 changes: 6 additions & 6 deletions lib/tls/tls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func TestGetClientTLSConfig(t *testing.T) {
t.Errorf("Failed to get absolute path for client TLS config: %s", err)
}

_, err = GetClientTLSConfig(cfg)
_, err = GetClientTLSConfig(cfg, nil)
if err != nil {
t.Errorf("Failed to get TLS Config: %s", err)
}
Expand All @@ -74,7 +74,7 @@ func TestGetClientTLSConfigInvalidArgs(t *testing.T) {
CertFile: "no_tls_client-cert.pem",
},
}
_, err := GetClientTLSConfig(cfg)
_, err := GetClientTLSConfig(cfg, nil)
assert.Error(t, err)
assert.Contains(t, err.Error(), "open no_tls_client-cert.pem: no such file or directory")

Expand All @@ -87,7 +87,7 @@ func TestGetClientTLSConfigInvalidArgs(t *testing.T) {
},
}
AbsTLSClient(cfg, configDir)
_, err = GetClientTLSConfig(cfg)
_, err = GetClientTLSConfig(cfg, nil)
assert.Error(t, err)
assert.Contains(t, err.Error(), "No TLS certificate files were provided")

Expand All @@ -100,7 +100,7 @@ func TestGetClientTLSConfigInvalidArgs(t *testing.T) {
},
}
AbsTLSClient(cfg, configDir)
_, err = GetClientTLSConfig(cfg)
_, err = GetClientTLSConfig(cfg, nil)
assert.Error(t, err)
assert.Contains(t, err.Error(), "no-tls_client-key.pem: no such file or directory")

Expand All @@ -112,7 +112,7 @@ func TestGetClientTLSConfigInvalidArgs(t *testing.T) {
CertFile: "",
},
}
_, err = GetClientTLSConfig(cfg)
_, err = GetClientTLSConfig(cfg, nil)
assert.Error(t, err)
assert.Contains(t, err.Error(), "No TLS certificate files were provided")

Expand All @@ -125,7 +125,7 @@ func TestGetClientTLSConfigInvalidArgs(t *testing.T) {
},
}
AbsTLSClient(cfg, configDir)
_, err = GetClientTLSConfig(cfg)
_, err = GetClientTLSConfig(cfg, nil)
assert.Error(t, err)
assert.Contains(t, err.Error(), "no-root.pem: no such file or directory")
}
Expand Down
4 changes: 3 additions & 1 deletion util/csp.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,14 @@ func BccspBackedSigner(caFile, keyFile string, policy *config.Signing, csp bccsp
// Fallback: attempt to read out of keyFile and import
log.Debugf("No key found in BCCSP keystore, attempting fallback")
var key bccsp.Key
var signer crypto.Signer

key, err = ImportBCCSPKeyFromPEM(keyFile, csp, false)
if err != nil {
return nil, fmt.Errorf("Could not find the private key in BCCSP keystore nor in keyfile %s: %s", keyFile, err)
}

signer, err := cspsigner.New(csp, key)
signer, err = cspsigner.New(csp, key)
if err != nil {
return nil, fmt.Errorf("Failed initializing CryptoSigner: %s", err)
}
Expand Down

0 comments on commit f963ce8

Please sign in to comment.