Skip to content

Commit

Permalink
[FAB-5058] Auto generate TLS certificates
Browse files Browse the repository at this point in the history
Fabric-CA server should automatically generate
TLS certificate if TLS enabled is on the server
but no TLS certificate and key are provided.

Change-Id: I57dbd9c14e4100fde4af78a39c10495bba8b1fc2
Signed-off-by: Saad Karim <[email protected]>
  • Loading branch information
Saad Karim committed Aug 22, 2017
1 parent a3ebb1f commit f8a910f
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 5 deletions.
4 changes: 2 additions & 2 deletions cmd/fabric-ca-server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ tls:
# Enable TLS (default: false)
enabled: false
# TLS for the server's listening port
certfile: ca-cert.pem
keyfile: ca-key.pem
certfile: tls-cert.pem
keyfile:
clientauth:
type: noclientcert
certfiles:
Expand Down
6 changes: 6 additions & 0 deletions lib/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,12 @@ func (ca *CA) initConfig() (err error) {
&cs.Default,
defaultIssuedCertificateExpiration,
false)
tlsProfile := cs.Profiles["tls"]
initSigningProfile(&tlsProfile,
defaultIssuedCertificateExpiration,
false)
cs.Profiles["tls"] = tlsProfile

// Set log level if debug is true
if ca.server != nil && ca.server.Config != nil && ca.server.Config.Debug {
log.Level = log.LevelDebug
Expand Down
76 changes: 75 additions & 1 deletion lib/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (

"github.com/cloudflare/cfssl/log"
"github.com/cloudflare/cfssl/revoke"
"github.com/cloudflare/cfssl/signer"
stls "github.com/hyperledger/fabric-ca/lib/tls"
"github.com/hyperledger/fabric-ca/util"
"github.com/spf13/viper"
Expand Down Expand Up @@ -197,7 +198,16 @@ func (s *Server) initConfig() (err error) {
if err != nil {
return fmt.Errorf("Failed to get server's home directory: %s", err)
}

}

// Make home directory absolute, if not already
absoluteHomeDir, err := filepath.Abs(s.HomeDir)
if err != nil {
return fmt.Errorf("Failed to make server's home directory path absolute: %s", err)
}
s.HomeDir = absoluteHomeDir

// Create config if not set
if s.Config == nil {
s.Config = new(ServerConfig)
Expand Down Expand Up @@ -440,6 +450,29 @@ func (s *Server) listenAndServe() (err error) {
if c.TLS.Enabled {
log.Debug("TLS is enabled")
addrStr = fmt.Sprintf("https://%s", addr)

if c.TLS.CertFile == "" {
return errors.New("TLS is enabled but no TLS certificate provided")
}

// If key file provided but certificate file does not exist than need to error out and not start server
if c.TLS.KeyFile != "" && !util.FileExists(c.TLS.CertFile) {
if util.FileExists(c.TLS.KeyFile) {
return fmt.Errorf("Found key file '%s' but could not find certificate file '%s'. Please check your configuration", c.TLS.KeyFile, c.TLS.CertFile)
}
return fmt.Errorf("Certificate file '%s' does not exist; key file '%s' must not be specified in order to automatically generate the key", c.TLS.CertFile, c.TLS.KeyFile)
}

// TLS enabled but no TLS certificate exists
if !util.FileExists(c.TLS.CertFile) {
err = s.autoGenerateTLSCertificateKey()
if err != nil {
return fmt.Errorf("Failed to automatically generate TLS certificate and key: %s", err)
}
}

log.Debugf("TLS Certificate: %s, TLS Key: %s", c.TLS.CertFile, c.TLS.KeyFile)

cer, err := util.LoadX509KeyPair(c.TLS.CertFile, c.TLS.KeyFile, s.csp)
if err != nil {
return err
Expand Down Expand Up @@ -484,7 +517,7 @@ func (s *Server) listenAndServe() (err error) {
}
}
s.listener = listener
log.Infof("Listening on %s", s.Config.Port, addrStr)
log.Infof("Listening on %s", addrStr)

err = s.checkAndEnableProfiling()
if err != nil {
Expand Down Expand Up @@ -636,6 +669,47 @@ func (s *Server) loadDNFromCertFile(certFile string) (*DN, error) {
return distinguishedName, nil
}

func (s *Server) autoGenerateTLSCertificateKey() error {
log.Debug("TLS enabled but no certificate or key provided, automatically generate TLS credentials")

clientCfg := &ClientConfig{
CSP: s.CA.Config.CSP,
}
client := Client{
HomeDir: s.HomeDir,
Config: clientCfg,
}

// Generate CSR that will be used to create the TLS certificate
csrReq := s.Config.CAcfg.CSR
csrReq.CA = nil // Not requesting a CA certificate
hostname := util.Hostname()
log.Debugf("TLS CSR: %+v\n", csrReq)

// Can't use the same CN as the signing certificate CN (default: fabric-ca-server) otherwise no AKI is generated
csr, _, err := client.GenCSR(&csrReq, hostname)
if err != nil {
return fmt.Errorf("Failed to generate CSR: %s", err)
}

// Use the 'tls' profile that will return a certificate with the appropriate extensions
req := signer.SignRequest{
Profile: "tls",
Request: string(csr),
}

// Use default CA to get back signed TLS certificate
cert, err := s.CA.enrollSigner.Sign(req)
if err != nil {
return fmt.Errorf("Failed to generate TLS certificate: %s", err)
}

// Write the TLS certificate to the file system
ioutil.WriteFile(s.Config.TLS.CertFile, cert, 0644)

return nil
}

func (dn *DN) equal(checkDN *DN) error {
log.Debugf("Check to see if two DNs are equal - %+v and %+v", dn, checkDN)
if dn.issuer == checkDN.issuer {
Expand Down
79 changes: 79 additions & 0 deletions lib/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1758,6 +1758,72 @@ func TestCSRInputLengthCheck(t *testing.T) {
}
}

func TestAutoTLSCertificateGeneration(t *testing.T) {
os.RemoveAll(rootDir)
defer os.RemoveAll(rootDir)
srv := TestGetRootServer(t)

srv.Config.TLS.Enabled = true
srv.Config.TLS.CertFile = "tls-cert.pem"
srv.Config.CAcfg.CSR.CN = "fabric-ca-server"
srv.Config.CAcfg.CSR.Hosts = []string{"localhost"}

err := srv.Start()
if !assert.NoError(t, err, "Failed to start server") {
t.Fatalf("Failed to start server: %s", err)
}

cert, err := util.GetX509CertificateFromPEMFile(srv.Config.TLS.CertFile)
assert.NoError(t, err, "Failed to get certificate")

// Check if the certificate has correct extended key usages
clientAuth := false
serverAuth := false
for _, usage := range cert.ExtKeyUsage {
if usage == x509.ExtKeyUsageClientAuth {
clientAuth = true
}
if usage == x509.ExtKeyUsageServerAuth {
serverAuth = true
}
}

if !clientAuth || !serverAuth {
t.Error("Certificate does not have correct extended key usage. Should have ExtKeyUsageServerAuth and ExtKeyUsageClientAuth")
}

trustedTLSCert, err := filepath.Abs(srv.CA.Config.CA.Certfile)
trustedTLSCerts := []string{trustedTLSCert}

// Test enrolling with with client using TLS
client := getTLSTestClient(7075, trustedTLSCerts)
enrollReq := &api.EnrollmentRequest{
Name: "admin",
Secret: "adminpw",
}
_, err = client.Enroll(enrollReq)
assert.NoError(t, err, "Error occured during enrollment on TLS enabled fabric-ca server")

err = srv.Stop()
assert.NoError(t, err, "Failed to stop server")

// Test the case where TLS key is provided but TLS certificate does not exist
srv.Config.TLS.CertFile = "fake-tls-cert.pem"
srv.Config.TLS.KeyFile = "key.pem"

err = srv.Start()
if assert.Error(t, err, "Should have failed to start server where TLS key is specified but certificate does not exist") {
assert.Contains(t, err.Error(), "must not be specified in order to automatically generate the key")
}

// TLS certificate does not exist
srv.Config.TLS.CertFile = ""
err = srv.Start()
if assert.Error(t, err, "Should have failed to start server where, no TLS certificate provided") {
assert.Contains(t, err.Error(), "TLS is enabled but no TLS certificate provided")
}
}

func TestEnd(t *testing.T) {
TestSRVServerClean(t)
}
Expand Down Expand Up @@ -1863,6 +1929,19 @@ func getTestClient(port int) *Client {
}
}

func getTLSTestClient(port int, trustedTLSCerts []string) *Client {
return &Client{
Config: &ClientConfig{
URL: fmt.Sprintf("https://localhost:%d", port),
TLS: libtls.ClientTLSConfig{
Enabled: true,
CertFiles: trustedTLSCerts,
},
},
HomeDir: testdataDir,
}
}

func getTLSConfig(srv *Server, clientAuthType string, clientRootCerts []string) *Server {
srv.Config.TLS.Enabled = true
srv.Config.TLS.CertFile = "../testdata/tls_server-cert.pem"
Expand Down
4 changes: 2 additions & 2 deletions lib/tls/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ import (
// ServerTLSConfig defines key material for a TLS server
type ServerTLSConfig struct {
Enabled bool `help:"Enable TLS on the listening port"`
CertFile string `def:"ca-cert.pem" help:"PEM-encoded TLS certificate file for server's listening port"`
KeyFile string `def:"ca-key.pem" help:"PEM-encoded TLS key for server's listening port"`
CertFile string `def:"tls-cert.pem" help:"PEM-encoded TLS certificate file for server's listening port"`
KeyFile string `help:"PEM-encoded TLS key for server's listening port"`
ClientAuth ClientAuth
}

Expand Down
9 changes: 9 additions & 0 deletions util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -688,3 +688,12 @@ func Read(r io.Reader, data []byte) ([]byte, error) {

return data[:j], nil
}

// Hostname name returns the hostname of the machine
func Hostname() string {
hostname, _ := os.Hostname()
if hostname == "" {
hostname = "localhost"
}
return hostname
}

0 comments on commit f8a910f

Please sign in to comment.