Skip to content

Commit

Permalink
[FAB-9243] Add ability to get CA's idemix public key
Browse files Browse the repository at this point in the history
Changes to the getcainfo/getcacert command to get
CA idemix public key in addition to the X509 CA chain. It will
be stored at <client msp>/IssuerPublicKey

Changed getcacert command to getcainfo and added an alias
so getcacert command will also work for backward compatibility.

On the server, CA's Idemix public key will be stored in the
CA home directory and secret key will be stored in the msp/keystore
directory.

Change-Id: Ie79ccb9491171eae81a2be972afac3807f10e437
Signed-off-by: Anil Ambati <[email protected]>
  • Loading branch information
Anil Ambati committed May 11, 2018
1 parent 534af8c commit f718bb5
Show file tree
Hide file tree
Showing 22 changed files with 515 additions and 107 deletions.
18 changes: 15 additions & 3 deletions cmd/fabric-ca-client/command/clientcmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,21 @@ const (
register = "register"
revoke = "revoke"
getcacert = "getcacert"
getcainfo = "getcainfo"
gencsr = "gencsr"
)

// Command interface initializes client command and loads an identity
type Command interface {
// Initializes the client command configuration
ConfigInit() error
// Returns the name of the configuration file
GetCfgFileName() string
// Loads the credentials of an identity that are in the msp directory specified to this command
LoadMyIdentity() (*lib.Identity, error)
// Returns lib.ClientCfg instance associated with this comamnd
GetClientCfg() *lib.ClientConfig
// Returns viper instance associated with this comamnd
GetViper() *viper.Viper
}

Expand Down Expand Up @@ -154,10 +161,10 @@ func (c *ClientCmd) init() {
},
}
c.rootCmd.AddCommand(c.newRegisterCommand(),
c.newEnrollCommand(),
newEnrollCmd(c).getCommand(),
c.newReenrollCommand(),
c.newRevokeCommand(),
c.newGetCACertCommand(),
newGetCAInfoCmd(c).getCommand(),
c.newGenCsrCommand(),
c.newGenCRLCommand(),
c.newIdentityCommand(),
Expand Down Expand Up @@ -244,7 +251,7 @@ func (c *ClientCmd) checkAndEnableProfiling() error {
// Certain client commands can only be executed if enrollment credentials
// are present
func (c *ClientCmd) requiresEnrollment() bool {
return c.name != enroll && c.name != getcacert && c.name != gencsr
return c.name != enroll && c.name != getcacert && c.name != getcainfo && c.name != gencsr
}

// Create default client configuration file only during an enroll or gencsr command
Expand Down Expand Up @@ -276,6 +283,11 @@ func (c *ClientCmd) GetClientCfg() *lib.ClientConfig {
return c.clientCfg
}

// GetCfgFileName returns name of the client command configuration file
func (c *ClientCmd) GetCfgFileName() string {
return c.cfgFileName
}

// GetViper returns the viper instance
func (c *ClientCmd) GetViper() *viper.Viper {
return c.myViper
Expand Down
2 changes: 1 addition & 1 deletion cmd/fabric-ca-client/command/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ func (c *ClientCmd) ConfigInit() error {

log.Debugf("Home directory: %s", c.homeDirectory)

// Set configuration file name for viper and configure it read env variables
// Set configuration file name for viper and configure it to read env variables
c.myViper.SetConfigFile(c.cfgFileName)
c.myViper.AutomaticEnv() // read in environment variables that match

Expand Down
84 changes: 43 additions & 41 deletions cmd/fabric-ca-client/command/enroll.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,71 +27,73 @@ import (
"github.com/spf13/cobra"
)

func (c *ClientCmd) newEnrollCommand() *cobra.Command {
enrollCmd := &cobra.Command{
Use: "enroll -u http://user:userpw@serverAddr:serverPort",
Short: "Enroll an identity",
Long: "Enroll identity with Fabric CA server",
// PreRunE block for this command will check to make sure username
// and secret provided for the enroll command before creating and/or
// reading configuration file
PreRunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
return errors.Errorf(extraArgsError, args, cmd.UsageString())
}

err := c.ConfigInit()
if err != nil {
return err
}

log.Debugf("Client configuration settings: %+v", c.clientCfg)

return nil
},

RunE: func(cmd *cobra.Command, args []string) error {
err := c.runEnroll(cmd)
if err != nil {
return err
}
return nil
},
}
type enrollCmd struct {
Command
}

func newEnrollCmd(c Command) *enrollCmd {
enrollCmd := &enrollCmd{c}
return enrollCmd
}

// The client enroll main logic
func (c *ClientCmd) runEnroll(cmd *cobra.Command) error {
func (c *enrollCmd) getCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "enroll -u http://user:userpw@serverAddr:serverPort",
Short: "Enroll an identity",
Long: "Enroll identity with Fabric CA server",
PreRunE: c.preRunEnroll,
RunE: c.runEnroll,
}
return cmd
}

func (c *enrollCmd) preRunEnroll(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
return errors.Errorf(extraArgsError, args, cmd.UsageString())
}

err := c.ConfigInit()
if err != nil {
return err
}

log.Debugf("Client configuration settings: %+v", c.GetClientCfg())

return nil
}

func (c *enrollCmd) runEnroll(cmd *cobra.Command, args []string) error {
log.Debug("Entered runEnroll")
resp, err := c.clientCfg.Enroll(c.clientCfg.URL, filepath.Dir(c.cfgFileName))
cfgFileName := c.GetCfgFileName()
cfg := c.GetClientCfg()
resp, err := cfg.Enroll(cfg.URL, filepath.Dir(cfgFileName))
if err != nil {
return err
}

ID := resp.Identity

cfgFile, err := ioutil.ReadFile(c.cfgFileName)
cfgFile, err := ioutil.ReadFile(cfgFileName)
if err != nil {
return errors.Wrapf(err, "Failed to read file at '%s'", c.cfgFileName)
return errors.Wrapf(err, "Failed to read file at '%s'", cfgFileName)
}

cfg := strings.Replace(string(cfgFile), "<<<ENROLLMENT_ID>>>", ID.GetName(), 1)
cfgStr := strings.Replace(string(cfgFile), "<<<ENROLLMENT_ID>>>", ID.GetName(), 1)

err = ioutil.WriteFile(c.cfgFileName, []byte(cfg), 0644)
err = ioutil.WriteFile(cfgFileName, []byte(cfgStr), 0644)
if err != nil {
return errors.Wrapf(err, "Failed to write file at '%s'", c.cfgFileName)
return errors.Wrapf(err, "Failed to write file at '%s'", cfgFileName)
}

err = ID.Store()
if err != nil {
return errors.WithMessage(err, "Failed to store enrollment information")
}

err = storeCAChain(c.clientCfg, &resp.ServerInfo)
// Store issuer public key
err = storeCAChain(cfg, &resp.ServerInfo)
if err != nil {
return err
}

return nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,62 +36,76 @@ import (
"github.com/spf13/cobra"
)

func (c *ClientCmd) newGetCACertCommand() *cobra.Command {
getCACertCmd := &cobra.Command{
Use: "getcacert -u http://serverAddr:serverPort -M <MSP-directory>",
Short: "Get CA certificate chain",
// PreRunE block for this command will load client configuration
// before running the command
PreRunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
return errors.Errorf(extraArgsError, args, cmd.UsageString())
}
const (
// GetCAInfoCmdUsage is the usage text for getCACert command
GetCAInfoCmdUsage = "getcainfo -u http://serverAddr:serverPort -M <MSP-directory>"
// GetCAInfoCmdShortDesc is the short description for getCACert command
GetCAInfoCmdShortDesc = "Get CA certificate chain and Idemix public key"
)

err := c.ConfigInit()
if err != nil {
return err
}
type getCAInfoCmd struct {
Command
}

log.Debugf("Client configuration settings: %+v", c.clientCfg)
func newGetCAInfoCmd(c Command) *getCAInfoCmd {
getcacertcmd := &getCAInfoCmd{c}
return getcacertcmd
}

return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
err := c.runGetCACert()
if err != nil {
return err
}
return nil
},
func (c *getCAInfoCmd) getCommand() *cobra.Command {
cmd := &cobra.Command{
Use: GetCAInfoCmdUsage,
Short: GetCAInfoCmdShortDesc,
Aliases: []string{"getcacert"},
PreRunE: c.preRunGetCACert,
RunE: c.runGetCACert,
}
return getCACertCmd
return cmd
}

// The client "getcacert" main logic
func (c *ClientCmd) runGetCACert() error {
func (c *getCAInfoCmd) preRunGetCACert(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
return errors.Errorf(extraArgsError, args, cmd.UsageString())
}

err := c.ConfigInit()
if err != nil {
return err
}

log.Debugf("Client configuration settings: %+v", c.GetClientCfg())

return nil
}

func (c *getCAInfoCmd) runGetCACert(cmd *cobra.Command, args []string) error {
log.Debug("Entered runGetCACert")

client := &lib.Client{
HomeDir: filepath.Dir(c.cfgFileName),
Config: c.clientCfg,
HomeDir: filepath.Dir(c.GetCfgFileName()),
Config: c.GetClientCfg(),
}

req := &api.GetCAInfoRequest{
CAName: c.clientCfg.CAName,
CAName: c.GetClientCfg().CAName,
}

si, err := client.GetCAInfo(req)
if err != nil {
return err
}

return storeCAChain(client.Config, si)
err = storeCAChain(client.Config, si)
if err != nil {
return err
}
return storeIssuerPublicKey(client.Config, si)
}

// Store the CAChain in the CACerts folder of MSP (Membership Service Provider)
// The root cert in the chain goes into MSP 'cacerts' directory.
// The others (if any) go into the MSP 'intermediatecerts' directory.
func storeCAChain(config *lib.ClientConfig, si *lib.GetServerInfoResponse) error {
func storeCAChain(config *lib.ClientConfig, si *lib.GetCAInfoResponse) error {
mspDir := config.MSPDir
// Get a unique name to use for filenames
serverURL, err := url.Parse(config.URL)
Expand Down Expand Up @@ -173,6 +187,16 @@ func storeCAChain(config *lib.ClientConfig, si *lib.GetServerInfoResponse) error
return nil
}

func storeIssuerPublicKey(config *lib.ClientConfig, si *lib.GetCAInfoResponse) error {
if len(si.IssuerPublicKey) > 0 {
err := storeCert("Issuer public key", config.MSPDir, "IssuerPublicKey", si.IssuerPublicKey)
if err != nil {
return err
}
}
return nil
}

func storeCert(what, dir, fname string, contents []byte) error {
err := os.MkdirAll(dir, 0755)
if err != nil {
Expand Down
76 changes: 76 additions & 0 deletions cmd/fabric-ca-client/command/getcainfo_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
Copyright IBM Corp. 2017 All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package command

import (
"reflect"
"testing"

"github.com/hyperledger/fabric-ca/cmd/fabric-ca-client/mocks"
"github.com/hyperledger/fabric-ca/lib"
"github.com/pkg/errors"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
)

func TestNewGetCACertCmd(t *testing.T) {
cmd := new(mocks.Command)
cmd.On("GetViper").Return(viper.New())
getcacertCmd := newGetCAInfoCmd(cmd)
assert.NotNil(t, getcacertCmd)
}

func TestGetCommand(t *testing.T) {
cmd := new(mocks.Command)
cmd.On("GetViper").Return(viper.New())
getcacertCmd := newGetCAInfoCmd(cmd)
cobraCmd := getcacertCmd.getCommand()
assert.NotNil(t, cobraCmd)
assert.Equal(t, cobraCmd.Name(), "getcainfo")
assert.Equal(t, cobraCmd.Short, GetCAInfoCmdShortDesc)
assert.Equal(t, cobraCmd.Use, GetCAInfoCmdUsage)

f1 := reflect.ValueOf(cobraCmd.PreRunE)
f2 := reflect.ValueOf(getcacertCmd.preRunGetCACert)
assert.Equal(t, f1.Pointer(), f2.Pointer(), "PreRunE function variable of the getcacert cobra command must be preRunGetCACert function of the getCACert struct")

f1 = reflect.ValueOf(cobraCmd.RunE)
f2 = reflect.ValueOf(getcacertCmd.runGetCACert)
assert.Equal(t, f1.Pointer(), f2.Pointer(), "RunE function variable of the getcacert cobra command must be runGetCACert function of the getCACert struct")
}

func TestBadConfigPreRunGetCACert(t *testing.T) {
cmd := new(mocks.Command)
cmd.On("GetViper").Return(viper.New())
cmd.On("ConfigInit").Return(errors.New("Failed to initialize config"))
getcacertCmd := newGetCAInfoCmd(cmd)
cobraCmd := getcacertCmd.getCommand()
err := getcacertCmd.preRunGetCACert(cobraCmd, []string{})
assert.Error(t, err)
assert.Equal(t, err.Error(), "Failed to initialize config")
}

func TestGoodConfigPreRunGetCACert(t *testing.T) {
cmd := new(mocks.Command)
cmd.On("GetViper").Return(viper.New())
cmd.On("ConfigInit").Return(nil)
cmd.On("GetClientCfg").Return(&lib.ClientConfig{})
getcacertCmd := newGetCAInfoCmd(cmd)
cobraCmd := getcacertCmd.getCommand()
err := getcacertCmd.preRunGetCACert(cobraCmd, []string{})
assert.NoError(t, err)
}
Loading

0 comments on commit f718bb5

Please sign in to comment.