Skip to content

Commit

Permalink
Add support for client getcacert command
Browse files Browse the repository at this point in the history
See https://jira.hyperledger.org/browse/FAB-2602

This change set implements the "fabric-ca-client getcacert" command
as described in FAB-2602.  A follow on change set will implement
the "-M" option of the enroll command.

Change-Id: Ida82e7c4ecf8a46a52d15cb58546185d869561c4
Signed-off-by: Keith Smith <[email protected]>
  • Loading branch information
Keith Smith committed Mar 11, 2017
1 parent df922a1 commit 2e51747
Show file tree
Hide file tree
Showing 18 changed files with 420 additions and 67 deletions.
2 changes: 1 addition & 1 deletion api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ type RegistrationRequest struct {
// RegistrationResponse is a registration response
type RegistrationResponse struct {
// The secret returned from a successful registration response
Secret string `json:"credential,omitempty"`
Secret string `json:"secret"`
}

// EnrollmentRequest is a request to enroll an identity
Expand Down
8 changes: 8 additions & 0 deletions cmd/fabric-ca-client/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ const (
# URL of the Fabric-ca-server (default: http://localhost:7054)
URL: <<<URL>>>
# Membership Service Provider (MSP) directory
# This is useful when the client is used to enroll a peer or orderer, so
# that the enrollment artifacts are stored in the format expected by MSP.
MSPDir:
#############################################################################
# TLS section for the client's listenting port
#############################################################################
Expand Down Expand Up @@ -228,6 +233,9 @@ func createDefaultConfigFile() error {
cfg = strings.Replace(defaultCfgTemplate, "<<<URL>>>", fabricCAServerURL, 1)
cfg = strings.Replace(cfg, "<<<MYHOST>>>", myhost, 1)
user, _, err := util.GetUser()
if err != nil {
return err
}
cfg = strings.Replace(cfg, "<<<ENROLLMENT_ID>>>", user, 1)

// Now write the file
Expand Down
89 changes: 89 additions & 0 deletions cmd/fabric-ca-client/getcacert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
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 main

import (
"fmt"
"os"
"path"
"path/filepath"
"strings"

"github.com/cloudflare/cfssl/log"
"github.com/hyperledger/fabric-ca/lib"
"github.com/hyperledger/fabric-ca/util"
"github.com/spf13/cobra"
)

// getCACertCmd represents the "getcacert" command
var getCACertCmd = &cobra.Command{
Use: "getcacert -u http://serverAddr:serverPort -M <MSP-directory>",
Short: "Get CA certificate chain",
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
cmd.Help()
return nil
}
err := runGetCACert()
if err != nil {
return err
}
return nil
},
}

func init() {
rootCmd.AddCommand(getCACertCmd)
}

// The client "getcacert" main logic
func runGetCACert() error {
log.Debug("Entered runGetCACert")

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

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

return storeCAChain(client, si)
}

// Store the CAChain in the CACerts folder of MSP (Membership Service Provider)
func storeCAChain(client *lib.Client, si *lib.GetServerInfoResponse) error {
mspDir := client.Config.MSPDir
if !util.FileExists(mspDir) {
return fmt.Errorf("Directory does not exist: %s", mspDir)
}
caCertsDir := path.Join(mspDir, "cacerts")
err := os.MkdirAll(caCertsDir, 0755)
if err != nil {
return fmt.Errorf("Failed creating CA certificates directory: %s", err)
}
fname := strings.Replace(si.CAName, ".", "-", -1) + ".pem"
path := path.Join(caCertsDir, fname)
err = util.WriteFile(path, si.CAChain, 0644)
if err != nil {
return fmt.Errorf("Failed to create CA root file: %s", err)
}
log.Infof("Stored CA certificate chain at %s", path)
return nil
}
23 changes: 23 additions & 0 deletions cmd/fabric-ca-client/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ func TestClientCommandsNoTLS(t *testing.T) {
t.Errorf("Server start failed: %s", err)
}

testGetCACert(t)
testEnroll(t)
testRegisterConfigFile(t)
testRegisterEnvVar(t)
Expand All @@ -129,6 +130,28 @@ func TestClientCommandsNoTLS(t *testing.T) {
}
}

// TestGetCACert tests fabric-ca-client getcacert
func testGetCACert(t *testing.T) {
t.Log("Testing getcacert")
defYaml = util.GetDefaultConfigFile("fabric-ca-client")
os.Remove(defYaml) // Clean up any left over config file
os.RemoveAll("cacerts")
err := RunMain([]string{cmdName, "getcacert", "-d", "-u", "http://localhost:7054"})
if err != nil {
t.Errorf("getcainfo failed: %s", err)
}
err = RunMain([]string{cmdName, "getcacert", "-d", "-u", "http://localhost:9999"})
if err == nil {
t.Error("getcacert with bogus URL should have failed but did not")
}
err = RunMain([]string{cmdName, "getcacert", "-d"})
if err == nil {
t.Error("getcacert with no URL should have failed but did not")
}
os.RemoveAll("cacerts")
os.Remove(defYaml)
}

// TestEnroll tests fabric-ca-client enroll
func testEnroll(t *testing.T) {
t.Log("Testing Enroll CMD")
Expand Down
20 changes: 16 additions & 4 deletions cmd/fabric-ca-server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,24 @@ tls:
keyfile: ca-key.pem
#############################################################################
# The CA section contains the key and certificate files used when
# issuing enrollment certificates (ECerts) and transaction
# The CA section contains information related to the Certificate Authority
# including the name of the CA, which should be unique for all members
# of a blockchain network. It also includes the key and certificate files
# used when issuing enrollment certificates (ECerts) and transaction
# certificates (TCerts).
# The chainfile (if it exists) contains the certificate chain which
# should be trusted for this CA, where the 1st in the chain is always the
# root CA certificate.
#############################################################################
ca:
# Certificate file (default: ca-cert.pem)
certfile: ca-cert.pem
# Name of this CA
name: <<<CANAME>>>
# Key file (default: ca-key.pem)
keyfile: ca-key.pem
# Certificate file (default: ca-cert.pem)
certfile: ca-cert.pem
# Chain file (default: chain-cert.pem)
chainfile: ca-chain.pem
#############################################################################
# The registry section controls how the fabric-ca-server does two things:
Expand Down Expand Up @@ -301,10 +310,13 @@ func createDefaultConfigFile() error {
if err != nil {
return err
}
// Get domain name
mydomain := strings.Join(strings.Split(myhost, ".")[1:], ".")
// Do string subtitution to get the default config
cfg := strings.Replace(defaultCfgTemplate, "<<<ADMIN>>>", user, 1)
cfg = strings.Replace(cfg, "<<<ADMINPW>>>", pass, 1)
cfg = strings.Replace(cfg, "<<<MYHOST>>>", myhost, 1)
cfg = strings.Replace(cfg, "<<<CANAME>>>", mydomain, 1)
// Now write the file
err = os.MkdirAll(filepath.Dir(cfgFileName), 0755)
if err != nil {
Expand Down
81 changes: 65 additions & 16 deletions lib/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"github.com/hyperledger/fabric-ca/api"
"github.com/hyperledger/fabric-ca/lib/tls"
"github.com/hyperledger/fabric-ca/util"
"github.com/mitchellh/mapstructure"
)

const (
Expand Down Expand Up @@ -100,6 +101,37 @@ type Client struct {
Config *ClientConfig
}

// GetServerInfoResponse is the response from the GetServerInfo call
type GetServerInfoResponse struct {
// CAName is the name of the CA
CAName string
// CAChain is the PEM-encoded bytes of the fabric-ca-server's CA chain.
// The 1st element of the chain is the root CA cert
CAChain []byte
}

// GetServerInfo returns generic server information
func (c *Client) GetServerInfo() (*GetServerInfoResponse, error) {
req, err := c.NewGet("info")
if err != nil {
return nil, err
}
sirn := &serverInfoResponseNet{}
err = c.SendReq(req, sirn)
if err != nil {
return nil, err
}
caChain, err := util.B64Decode(sirn.CAChain)
if err != nil {
return nil, err
}
si := &GetServerInfoResponse{
CAName: sirn.CAName,
CAChain: caChain,
}
return si, nil
}

// Enroll enrolls a new identity
// @param req The enrollment request
func (c *Client) Enroll(req *api.EnrollmentRequest) (*Identity, error) {
Expand Down Expand Up @@ -130,7 +162,8 @@ func (c *Client) Enroll(req *api.EnrollmentRequest) (*Identity, error) {
return nil, err
}
post.SetBasicAuth(req.Name, req.Secret)
result, err := c.SendPost(post)
var result string
err = c.SendReq(post, &result)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -275,11 +308,24 @@ func (c *Client) LoadCSRInfo(path string) (*api.CSRInfo, error) {
return &csrInfo, nil
}

// NewGet create a new GET request
func (c *Client) NewGet(endpoint string) (*http.Request, error) {
curl, err := c.getURL(endpoint)
if err != nil {
return nil, err
}
req, err := http.NewRequest("GET", curl, bytes.NewReader([]byte{}))
if err != nil {
return nil, fmt.Errorf("Failed creating GET request for %s: %s", curl, err)
}
return req, nil
}

// NewPost create a new post request
func (c *Client) NewPost(endpoint string, reqBody []byte) (*http.Request, error) {
curl, cerr := c.getURL(endpoint)
if cerr != nil {
return nil, cerr
curl, err := c.getURL(endpoint)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", curl, bytes.NewReader(reqBody))
if err != nil {
Expand All @@ -288,8 +334,8 @@ func (c *Client) NewPost(endpoint string, reqBody []byte) (*http.Request, error)
return req, nil
}

// SendPost sends a request to the LDAP server and returns a response
func (c *Client) SendPost(req *http.Request) (interface{}, error) {
// SendReq sends a request to the fabric-ca-server and fills in the result
func (c *Client) SendReq(req *http.Request, result interface{}) error {
reqStr := util.HTTPRequestToString(req)
log.Debugf("Sending request\n%s", reqStr)

Expand All @@ -300,12 +346,12 @@ func (c *Client) SendPost(req *http.Request) (interface{}, error) {

err := tls.AbsTLSClient(&c.Config.TLS, c.HomeDir)
if err != nil {
return nil, err
return err
}

tlsConfig, err := tls.GetClientTLSConfig(&c.Config.TLS)
if err != nil {
return nil, fmt.Errorf("Failed to get client TLS config: %s", err)
return fmt.Errorf("Failed to get client TLS config: %s", err)
}

tr.TLSClientConfig = tlsConfig
Expand All @@ -314,14 +360,14 @@ func (c *Client) SendPost(req *http.Request) (interface{}, error) {
httpClient := &http.Client{Transport: tr}
resp, err := httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("POST failure [%s]; not sending\n%s", err, reqStr)
return fmt.Errorf("POST failure [%s]; not sending\n%s", err, reqStr)
}
var respBody []byte
if resp.Body != nil {
respBody, err = ioutil.ReadAll(resp.Body)
defer resp.Body.Close()
if err != nil {
return nil, fmt.Errorf("Failed to read response [%s] of request:\n%s", err, reqStr)
return fmt.Errorf("Failed to read response [%s] of request:\n%s", err, reqStr)
}
log.Debugf("Received response\n%s", util.HTTPResponseToString(resp))
}
Expand All @@ -330,25 +376,28 @@ func (c *Client) SendPost(req *http.Request) (interface{}, error) {
body = new(cfsslapi.Response)
err = json.Unmarshal(respBody, body)
if err != nil {
return nil, fmt.Errorf("Failed to parse response [%s] for request:\n%s", err, reqStr)
return fmt.Errorf("Failed to parse response [%s] for request:\n%s", err, reqStr)
}
if len(body.Errors) > 0 {
msg := body.Errors[0].Message
return nil, fmt.Errorf("Error response from server was: %s", msg)
return fmt.Errorf("Error response from server was: %s", msg)
}
}
scode := resp.StatusCode
if scode >= 400 {
return nil, fmt.Errorf("Failed with server status code %d for request:\n%s", scode, reqStr)
return fmt.Errorf("Failed with server status code %d for request:\n%s", scode, reqStr)
}
if body == nil {
return nil, nil
return fmt.Errorf("Empty response body:\n%s", reqStr)
}
if !body.Success {
return nil, fmt.Errorf("Server returned failure for request:\n%s", reqStr)
return fmt.Errorf("Server returned failure for request:\n%s", reqStr)
}
log.Debugf("Response body result: %+v", body.Result)
return body.Result, nil
if result != nil {
return mapstructure.Decode(body.Result, result)
}
return nil
}

func (c *Client) getURL(endpoint string) (string, error) {
Expand Down
14 changes: 13 additions & 1 deletion lib/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ func TestClient(t *testing.T) {

c := getClient()

testGetServerInfo(c, t)
testRegister(c, t)
testEnrollIncorrectPassword(c, t)
testDoubleEnroll(c, t)
Expand All @@ -70,6 +71,17 @@ func TestClient(t *testing.T) {

}

func testGetServerInfo(c *Client, t *testing.T) {

si, err := c.GetServerInfo()
if err != nil {
t.Fatalf("Failed to get server info: %s", err)
}
if si == nil {
t.Fatal("Server info is nil")
}
}

func testRegister(c *Client, t *testing.T) {

// Enroll admin
Expand Down Expand Up @@ -326,7 +338,7 @@ func TestSendBadPost(t *testing.T) {
curl := "fake"
reqBody := []byte("")
req, _ := http.NewRequest("POST", curl, bytes.NewReader(reqBody))
_, err := c.SendPost(req)
err := c.SendReq(req, nil)
if err == nil {
t.Error("Sending post should have failed")
}
Expand Down
Loading

0 comments on commit 2e51747

Please sign in to comment.