Skip to content

Commit

Permalink
[FAB-3845] Configuration of intermediate CA via CLI
Browse files Browse the repository at this point in the history
Certain configurations can be set for an intermediate
CA. These configurations were missing command line
flags, preventing users from enrolling intermediate
CAs from the command line.

Added these missing flags to the fabric-ca server to
fix configuration of an intermediate CA using command
line flags.

The following flags are now available:
     --intermediate.enrollment.hosts
     --intermediate.enrollment.label
     --intermediate.enrollment.profile
     --intermediate.parentserver.caname
 -u, --intermediate.parentserver.url
     --intermediate.tls.certfiles
     --intermediate.tls.client.certfile
     --intermediate.tls.client.keyfile

Change-Id: I2526abc47d3ff054523b0fd23f7f75e6e9a71848
Signed-off-by: Saad Karim <[email protected]>
  • Loading branch information
Saad Karim committed May 17, 2017
1 parent 6deff4b commit de4187b
Show file tree
Hide file tree
Showing 14 changed files with 287 additions and 45 deletions.
31 changes: 31 additions & 0 deletions cmd/fabric-ca-client/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,13 @@ mspdir:
#############################################################################
# TLS section for secure socket connection
#
# certfiles - PEM-encoded list of trusted root certificate files
# client:
# certfile - PEM-encoded certificate file for when client authentication
# is enabled on server
# keyfile - PEM-encoded key file for when client authentication
# is enabled on server
#############################################################################
tls:
# TLS section for secure socket connection
Expand All @@ -103,6 +110,17 @@ tls:
# Certificate Signing Request section for generating the CSR for
# an enrollment certificate (ECert)
#
# cn - Used by CAs to determine which domain the certificate is to be generated for
# names - A list of name objects. Each name object should contain at least one
# "C", "L", "O", "OU", or "ST" value (or any combination of these). These values are:
# "C": country
# "L": locality or municipality (such as city or town name)
# "O": organisation
# "OU": organisational unit, such as the department responsible for owning the key;
# it can also be used for a "Doing Business As" (DBS) name
# "ST": the state or province
# hosts - A list of space-separated host names which the certificate should be valid for
#
# NOTE: The serialnumber field below, if specified, becomes part of the issued
# certificate's DN (Distinquished Name). For example, one use case for this is
# a company with its own CA (Certificate Authority) which issues certificates
Expand Down Expand Up @@ -131,24 +149,37 @@ csr:
#############################################################################
# Registration section used to register a new identity with fabric-ca server
#
# name - Unique name of the identity
# type - Type of identity being registered (e.g. 'peer, app, user')
# maxenrollments - The maximum number of times the secret can be reused to enroll
# affiliation - The identity's affiliation
# attributes - List of name/value pairs of attribute for identity
#############################################################################
id:
name:
type:
maxenrollments:
affiliation:
attributes:
- name:
value:
#############################################################################
# Enrollment section used to enroll an identity with fabric-ca server
#
# hosts - A comma-separated list of host names which the certificate should be valid for
# profile - Name of the signing profile to use in issuing the certificate
# label - Label to use in HSM operations
#############################################################################
enrollment:
hosts:
profile:
label:
#############################################################################
# Name of the CA to connect to within the fabric-ca server
#############################################################################
caname:
#############################################################################
Expand Down
4 changes: 2 additions & 2 deletions cmd/fabric-ca-client/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import (
"strings"
"testing"

"github.com/cloudflare/cfssl/csr"
"github.com/hyperledger/fabric-ca/api"
"github.com/hyperledger/fabric-ca/lib"
"github.com/hyperledger/fabric-ca/lib/dbutil"
"github.com/hyperledger/fabric-ca/util"
Expand Down Expand Up @@ -751,7 +751,7 @@ func getCAConfig() *lib.CAConfig {
Certfile: certfile,
},
Affiliations: affiliations,
CSR: csr.CertificateRequest{
CSR: api.CSRInfo{
CN: "TestCN",
},
}
Expand Down
37 changes: 37 additions & 0 deletions cmd/fabric-ca-server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,43 @@ bccsp:
cacount:
cafiles:
#############################################################################
# Intermediate CA which acts as a client of the root (or parent) CA.
#
# Parentserver section
# url - The URL of the parent server
# caname - Name of the CA to enroll with on the server
#
# Enrollment section used to enroll an identity with fabric-ca server
# hosts - A comma-separated list of host names which the certificate should
# be valid for
# profile - Name of the signing profile to use in issuing the certificate
# label - Label to use in HSM operations
#
# TLS section for secure socket connection
# certfiles - PEM-encoded list of trusted root certificate files
# client:
# certfile - PEM-encoded certificate file for when client authentication
# is enabled on server
# keyfile - PEM-encoded key file for when client authentication
# is enabled on server
#############################################################################
intermediate:
parentserver:
url:
caname:
enrollment:
hosts:
profile:
label:
tls:
certfiles:
client:
certfile:
keyfile:
`
)

Expand Down
15 changes: 10 additions & 5 deletions lib/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,14 +217,19 @@ func (ca *CA) initKeyMaterial(renew bool) error {

// Get the CA certificate for this CA
func (ca *CA) getCACert() (cert []byte, err error) {
log.Debugf("Getting CA cert; parent server URL is '%s'", ca.Config.ParentServer.URL)
if ca.Config.ParentServer.URL != "" {
log.Debugf("Getting CA cert; parent server URL is '%s'", ca.Config.Intermediate.ParentServer.URL)
if ca.Config.Intermediate.ParentServer.URL != "" {
// This is an intermediate CA, so call the parent fabric-ca-server
// to get the cert
clientCfg := ca.Config.Client
if clientCfg == nil {
clientCfg = &ClientConfig{}
}
// Copy over the intermediate configuration into client configuration
clientCfg.TLS = ca.Config.Intermediate.TLS
clientCfg.Enrollment = ca.Config.Intermediate.Enrollment
clientCfg.CAName = ca.Config.Intermediate.ParentServer.CAName
clientCfg.CSR = ca.Config.CSR
if clientCfg.Enrollment.Profile == "" {
clientCfg.Enrollment.Profile = "ca"
}
Expand All @@ -236,7 +241,7 @@ func (ca *CA) getCACert() (cert []byte, err error) {
}
log.Debugf("Intermediate enrollment request: %v", clientCfg.Enrollment)
var resp *EnrollmentResponse
resp, err = clientCfg.Enroll(ca.Config.ParentServer.URL, ca.HomeDir)
resp, err = clientCfg.Enroll(ca.Config.Intermediate.ParentServer.URL, ca.HomeDir)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -308,7 +313,7 @@ func (ca *CA) getCAChain() (chain []byte, err error) {
return util.ReadFile(certAuth.Chainfile)
}
// Otherwise, if this is a root CA, we always return the contents of the CACertfile
if ca.Config.ParentServer.URL == "" {
if ca.Config.Intermediate.ParentServer.URL == "" {
return util.ReadFile(certAuth.Certfile)
}
// If this is an intermediate CA but the ca.Chainfile doesn't exist,
Expand Down Expand Up @@ -457,7 +462,7 @@ func (ca *CA) initEnrollmentSigner() (err error) {
}

// Make sure the policy reflects the new remote
parentServerURL := ca.Config.ParentServer.URL
parentServerURL := ca.Config.Intermediate.ParentServer.URL
if parentServerURL != "" {
err = policy.OverrideRemotes(parentServerURL)
if err != nil {
Expand Down
16 changes: 13 additions & 3 deletions lib/caconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package lib

import (
"github.com/cloudflare/cfssl/config"
"github.com/cloudflare/cfssl/csr"
"github.com/hyperledger/fabric-ca/api"
"github.com/hyperledger/fabric-ca/lib/ldap"
"github.com/hyperledger/fabric-ca/lib/tls"
"github.com/hyperledger/fabric-ca/util"
Expand Down Expand Up @@ -69,15 +69,17 @@ csr:
// "skip" - to skip the field.
type CAConfig struct {
CA CAInfo
ParentServer ParentServer
Signing *config.Signing
CSR csr.CertificateRequest
CSR api.CSRInfo
Registry CAConfigRegistry
Affiliations map[string]interface{}
LDAP ldap.Config
DB CAConfigDB
CSP *factory.FactoryOpts `mapstructure:"bccsp"`
// Optional client config for an intermediate server which acts as a client
// of the root (or parent) server
Client *ClientConfig
Intermediate IntermediateCA
}

// CAInfo is the CA information on a fabric-ca-server
Expand Down Expand Up @@ -118,6 +120,14 @@ type ParentServer struct {
CAName string `help:"Name of the CA to connect to on fabric-ca-serve"`
}

// IntermediateCA contains parent server information, TLS configuration, and
// enrollment request for an intermetiate CA
type IntermediateCA struct {
ParentServer ParentServer
TLS tls.ClientTLSConfig
Enrollment api.EnrollmentRequest
}

func (cc *CAConfigIdentity) String() string {
return util.StructToString(cc)
}
8 changes: 4 additions & 4 deletions lib/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,10 @@ func (c *Client) Enroll(req *api.EnrollmentRequest) (*EnrollmentResponse, error)
CAName: req.CAName,
}

reqNet.Hosts = signer.SplitHosts(req.Hosts)
reqNet.Request = string(csrPEM)
reqNet.Profile = req.Profile
reqNet.Label = req.Label
reqNet.SignRequest.Hosts = signer.SplitHosts(req.Hosts)
reqNet.SignRequest.Request = string(csrPEM)
reqNet.SignRequest.Profile = req.Profile
reqNet.SignRequest.Label = req.Label

body, err := util.Marshal(reqNet, "SignRequest")
if err != nil {
Expand Down
7 changes: 4 additions & 3 deletions lib/client_whitebox_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ func getEnrollmentPayload(t *testing.T, c *Client) ([]byte, error) {

// Get the body of the request
sreq := signer.SignRequest{
Hosts: signer.SplitHosts(req.Hosts),
Request: string(csrPEM),
Profile: req.Profile,
Label: req.Label,
Expand Down Expand Up @@ -141,8 +140,10 @@ func getServer(port int, home, parentURL string, maxEnroll int, t *testing.T) *S
},
CA: CA{
Config: &CAConfig{
ParentServer: ParentServer{
URL: parentURL,
Intermediate: IntermediateCA{
ParentServer: ParentServer{
URL: parentURL,
},
},
Affiliations: affiliations,
Registry: CAConfigRegistry{
Expand Down
8 changes: 4 additions & 4 deletions lib/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,10 @@ func (i *Identity) Reenroll(req *api.ReenrollmentRequest) (*EnrollmentResponse,
}

// Get the body of the request
reqNet.Hosts = signer.SplitHosts(req.Hosts)
reqNet.Request = string(csrPEM)
reqNet.Profile = req.Profile
reqNet.Label = req.Label
reqNet.SignRequest.Hosts = signer.SplitHosts(req.Hosts)
reqNet.SignRequest.Request = string(csrPEM)
reqNet.SignRequest.Profile = req.Profile
reqNet.SignRequest.Label = req.Label

body, err := util.Marshal(reqNet, "SignRequest")
if err != nil {
Expand Down
9 changes: 3 additions & 6 deletions lib/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"strings"

"github.com/cloudflare/cfssl/log"
stls "github.com/hyperledger/fabric-ca/lib/tls"
"github.com/hyperledger/fabric-ca/util"
"github.com/spf13/viper"

Expand Down Expand Up @@ -509,13 +510,9 @@ func (s *Server) checkAndEnableProfiling() error {
// Make all file names in the config absolute
func (s *Server) makeFileNamesAbsolute() error {
log.Debug("Making server filenames absolute")
err := s.CA.makeFileNamesAbsolute()
err := stls.AbsTLSServer(&s.Config.TLS, s.HomeDir)
if err != nil {
return err
}
fields := []*string{
&s.Config.TLS.CertFile,
&s.Config.TLS.KeyFile,
}
return util.MakeFileNamesAbsolute(fields, s.HomeDir)
return nil
}
57 changes: 53 additions & 4 deletions lib/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,50 @@ func TestIntermediateServer(t *testing.T) {
}
}

func TestIntermediateServerWithTLS(t *testing.T) {
var err error

rootServer := getRootServer(t)
if rootServer == nil {
return
}
rootServer.Config.TLS.Enabled = true
rootServer.Config.TLS.CertFile = "../../testdata/tls_server-cert.pem"
rootServer.Config.TLS.KeyFile = "../../testdata/tls_server-key.pem"
rootServer.Config.TLS.ClientAuth.Type = "RequireAndVerifyClientCert"
rootServer.Config.TLS.ClientAuth.CertFiles = []string{"../../testdata/root.pem"}
err = rootServer.Start()
if err != nil {
t.Fatalf("Root server start failed: %s", err)
}

parentURL := fmt.Sprintf("https://admin:adminpw@localhost:%d", rootPort)
intermediateServer := getServer(intermediatePort, intermediateDir, parentURL, 0, t)
if intermediateServer == nil {
return
}
intermediateServer.CA.Config.Intermediate.TLS.CertFiles = []string{"../../testdata/root.pem"}
intermediateServer.CA.Config.Intermediate.TLS.Client.CertFile = "../../testdata/tls_client-cert.pem"
intermediateServer.CA.Config.Intermediate.TLS.Client.KeyFile = "../../testdata/tls_client-key.pem"

err = intermediateServer.Start()
if err != nil {
t.Errorf("Intermediate server start failed: %s", err)
}

time.Sleep(time.Second)

err = intermediateServer.Stop()
if err != nil {
t.Errorf("Intermediate server stop failed: %s", err)
}

err = rootServer.Stop()
if err != nil {
t.Errorf("Root server stop failed: %s", err)
}
}

func TestRunningTLSServer(t *testing.T) {
srv := getServer(rootPort, testdataDir, "", 0, t)

Expand Down Expand Up @@ -535,11 +579,14 @@ func TestMultiCAWithIntermediate(t *testing.T) {
// Start it
err = intermediatesrv.Start()
if err != nil {
t.Errorf("Intermediate server start failed: %s", err)
t.Errorf("Failed to start intermediate server: %s", err)
}
time.Sleep(time.Second)
// Stop it
intermediatesrv.Stop()
err = intermediatesrv.Stop()
if err != nil {
t.Error("Failed to stop intermediate server: ", err)
}

if !util.FileExists("../testdata/ca/intermediateca/ca1/ca-chain.pem") {
t.Error("Failed to enroll intermediate ca")
Expand Down Expand Up @@ -839,8 +886,10 @@ func getServer(port int, home, parentURL string, maxEnroll int, t *testing.T) *S
},
CA: CA{
Config: &CAConfig{
ParentServer: ParentServer{
URL: parentURL,
Intermediate: IntermediateCA{
ParentServer: ParentServer{
URL: parentURL,
},
},
Affiliations: affiliations,
Registry: CAConfigRegistry{
Expand Down
Loading

0 comments on commit de4187b

Please sign in to comment.