Skip to content

Commit

Permalink
[FAB-2896] Directing traffic to specific CAs
Browse files Browse the repository at this point in the history
This change-set is in a series of change-sets
to support multiple CAs. Traffic can now be directed
to a specific CA by providing the name of the CA in
the request to the server.

When using CA as intermediate you may specify
the parent server URL along with the name of
the CA to connect to with parent server.

Please note, a good chunk of lines come from
adding files in testdata folder to support
test cases where reading from config files
is required.

Next change-set will add support for cacount option
that will generate x number of default CAs.

Change-Id: I99ce151b46b1367d7f2e0fa1bf04f16f67145ff7
Signed-off-by: Saad Karim <[email protected]>
  • Loading branch information
Saad Karim committed Apr 25, 2017
1 parent 3ab84cb commit c131944
Show file tree
Hide file tree
Showing 28 changed files with 504 additions and 272 deletions.
23 changes: 19 additions & 4 deletions api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ type RegistrationRequest struct {
Attr string `help:"Attributes associated with this identity (e.g. hf.Revoker=true)"`
// Attributes associated with this identity
Attributes []Attribute `json:"attrs,omitempty"`
// CAName is the name of the CA to connect to
CAName string `skip:"true"`
}

// RegistrationResponse is a registration response
Expand All @@ -66,6 +68,8 @@ type EnrollmentRequest struct {
Label string `json:"label,omitempty" help:"Label to use in HSM operations"`
// CSR is Certificate Signing Request info
CSR *CSRInfo `json:"csr,omitempty" help:"Certificate Signing Request info"`
// CAName is the name of the CA to connect to
CAName string `skip:"true"`
}

// ReenrollmentRequest is a request to reenroll an identity.
Expand All @@ -79,6 +83,8 @@ type ReenrollmentRequest struct {
Label string `json:"label,omitempty"`
// CSR is Certificate Signing Request info
CSR *CSRInfo `json:"csr,omitempty"`
// CAName is the name of the CA to connect to
CAName string `skip:"true"`
}

// RevocationRequest is a revocation request for a single certificate or all certificates
Expand All @@ -90,15 +96,17 @@ type ReenrollmentRequest struct {
type RevocationRequest struct {
// Name of the identity whose certificates should be revoked
// If this field is omitted, then Serial and AKI must be specified.
Name string `json:"id,omitempty"`
Name string `json:"id,omitempty" help:"Identity whose certificates should be revoked"`
// Serial number of the certificate to be revoked
// If this is omitted, then Name must be specified
Serial string `json:"serial,omitempty"`
Serial string `json:"serial,omitempty" help:"Serial number of the certificate to be revoked"`
// AKI (Authority Key Identifier) of the certificate to be revoked
AKI string `json:"aki,omitempty"`
AKI string `json:"aki,omitempty" help:"AKI (Authority Key Identifier) of the certificate to be revoked"`
// Reason is the reason for revocation. See https://godoc.org/golang.org/x/crypto/ocsp for
// valid values. The default value is 0 (ocsp.Unspecified).
Reason int `json:"reason,omitempty"`
Reason string `json:"reason,omitempty" help:"Reason for revocation"`
// CAName is the name of the CA to connect to
CAName string `skip:"true"`
}

// GetTCertBatchRequest is input provided to identity.GetTCertBatch
Expand All @@ -120,13 +128,20 @@ type GetTCertBatchRequest struct {
// cryptographically related to an ECert. This may be necessary when using an
// HSM which does not support the TCert's key derivation function.
DisableKeyDerivation bool `json:"disable_kdf,omitempty"`
// CAName is the name of the CA to connect to
CAName string `skip:"true"`
}

// GetTCertBatchResponse is the return value of identity.GetTCertBatch
type GetTCertBatchResponse struct {
tcert.GetBatchResponse
}

// GetCAInfoRequest is request to get generic CA information
type GetCAInfoRequest struct {
CAName string `skip:"true"`
}

// CSRInfo is Certificate Signing Request information
type CSRInfo struct {
CN string `json:"CN"`
Expand Down
2 changes: 2 additions & 0 deletions api/net.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,14 @@ type RegistrationResponseNet struct {
// EnrollmentRequestNet is a request to enroll an identity
type EnrollmentRequestNet struct {
signer.SignRequest
CAName string
}

// ReenrollmentRequestNet is a request to reenroll an identity.
// This is useful to renew a certificate before it has expired.
type ReenrollmentRequestNet struct {
signer.SignRequest
CAName string
}

// RevocationRequestNet is a revocation request which flows over the network
Expand Down
7 changes: 6 additions & 1 deletion cmd/fabric-ca-client/getcacert.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"strings"

"github.com/cloudflare/cfssl/log"
"github.com/hyperledger/fabric-ca/api"
"github.com/hyperledger/fabric-ca/lib"
"github.com/hyperledger/fabric-ca/util"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -59,7 +60,11 @@ func runGetCACert() error {
Config: clientCfg,
}

si, err := client.GetServerInfo()
req := &api.GetCAInfoRequest{
CAName: clientCfg.CAInfo.CAName,
}

si, err := client.GetCAInfo(req)
if err != nil {
return err
}
Expand Down
3 changes: 1 addition & 2 deletions cmd/fabric-ca-client/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ var rootCmd = &cobra.Command{
util.CmdRunBegin()

cmd.SilenceUsage = true
cmd.SilenceErrors = true

return nil
},
Expand Down Expand Up @@ -81,7 +80,7 @@ func init() {
// The fabric-ca client main
func main() {
if err := RunMain(os.Args); err != nil {
util.Fatal("%s", err)
os.Exit(1)
}
}

Expand Down
83 changes: 65 additions & 18 deletions cmd/fabric-ca-client/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,26 +377,26 @@ func testRevoke(t *testing.T) {
t.Error(err)
}

// Revoker's affiliation: hyperledger.org1
err = RunMain([]string{cmdName, "revoke", "-u", "http://localhost:7054", "-e", "nonexistinguser"})
// Revoker's affiliation: company1
err = RunMain([]string{cmdName, "revoke", "-u", "http://localhost:7054", "--revoke.name", "nonexistinguser"})
if err == nil {
t.Errorf("Non existing user being revoked, should have failed")
}

err = RunMain([]string{cmdName, "revoke", "-u", "http://localhost:7054", "-e", "", "-s", serial})
err = RunMain([]string{cmdName, "revoke", "-u", "http://localhost:7054", "--revoke.name", "", "--revoke.serial", serial})
if err == nil {
t.Errorf("Only serial specified, should have failed")
}

err = RunMain([]string{cmdName, "revoke", "-u", "http://localhost:7054", "-e", "", "-s", "", "-a", aki})
err = RunMain([]string{cmdName, "revoke", "-u", "http://localhost:7054", "--revoke.name", "", "--revoke.serial", "", "--revoke.aki", aki})
if err == nil {
t.Errorf("Only aki specified, should have failed")
}

// revoker's affiliation: company1, revoking affiliation: ""
err = RunMain([]string{cmdName, "revoke", "-u", "http://localhost:7054", "-s", serial, "-a", aki})
err = RunMain([]string{cmdName, "revoke", "-u", "http://localhost:7054", "--revoke.serial", serial, "--revoke.aki", aki})
if err == nil {
t.Errorf("Should have failed, admin2 cannot revoke root affiliation")
t.Error("Should have failed, admin2 cannot revoke root affiliation")
}

// When serial, aki and enrollment id are specified in a revoke request,
Expand All @@ -408,21 +408,21 @@ func testRevoke(t *testing.T) {
}

// Revoked user's affiliation: hyperledger.org3
err = RunMain([]string{cmdName, "revoke", "-u", "http://localhost:7054", "-e", "testRegister3", "-s", "", "-a", ""})
err = RunMain([]string{cmdName, "revoke", "-u", "http://localhost:7054", "--revoke.name", "testRegister3", "--revoke.serial", "", "--revoke.aki", ""})
if err == nil {
t.Errorf("Should have failed, admin2 does not have authority revoke")
t.Error("Should have failed, admin2 does not have authority revoke")
}

// testRegister2's affiliation: company1, revoker's affiliation: company1
err = RunMain([]string{cmdName, "revoke", "-u", "http://localhost:7054", "-e", "testRegister2", "-s", "", "-a", ""})
err = RunMain([]string{cmdName, "revoke", "-u", "http://localhost:7054", "--revoke.name", "testRegister2", "--revoke.serial", "", "--revoke.aki", ""})
if err != nil {
t.Errorf("Failed to revoke proper affiliation hierarchy")
t.Errorf("Failed to revoke proper affiliation hierarchy, error: %s", err)
}

// testRegister4's affiliation: company2, revoker's affiliation: company1
err = RunMain([]string{cmdName, "revoke", "-u", "http://localhost:7054", "-e", "testRegister4", "-s", "", "-a", ""})
if err == nil {
t.Errorf("Should have failed have different affiliation path")
t.Error("Should have failed have different affiliation path")
}

// Enroll admin with root affiliation and test revoking with root
Expand All @@ -432,9 +432,10 @@ func testRevoke(t *testing.T) {
}

// testRegister4's affiliation: company2, revoker's affiliation: "" (root)
err = RunMain([]string{cmdName, "revoke", "-u", "http://localhost:7054", "-e", "testRegister4", "-s", "", "-a", ""})
err = RunMain([]string{cmdName, "revoke", "-u", "http://localhost:7054", "--revoke.name", "testRegister4", "--revoke.serial", "", "--revoke.aki", ""})
if err != nil {
t.Errorf("User with root affiliation failed to revoke")
t.Errorf("User with root affiliation failed to revoke, error: %s", err)

}

os.Remove(defYaml) // Delete default config file
Expand All @@ -446,6 +447,7 @@ func testRevoke(t *testing.T) {
}

os.RemoveAll(filepath.Dir(defYaml))

}

// TestBogus tests a negative test case
Expand Down Expand Up @@ -575,12 +577,55 @@ func TestClientCommandsTLS(t *testing.T) {
}
}

func TestMultiCA(t *testing.T) {
srv = getServer()
srv.HomeDir = "../../testdata"
srv.Config.CAfiles = []string{"ca/rootca/ca1/fabric-ca-server-config.yaml", "ca/rootca/ca2/fabric-ca-server-config.yaml"}
srv.CA.Config.CSR.Hosts = []string{"hostname"}
t.Logf("Server configuration: %+v\n", srv.Config)

srv.BlockingStart = false
err := srv.Start()
if err != nil {
t.Fatal("Failed to start server:", err)
}

err = RunMain([]string{cmdName, "enroll", "-c", testYaml, "-u", "http://adminca1:adminca1pw@localhost:7054", "-d", "--caname", "rootca1"})
if err != nil {
t.Errorf("client enroll -c -u failed: %s", err)
}

err = RunMain([]string{cmdName, "enroll", "-c", testYaml, "-u", "http://adminca1:adminca1pw@localhost:7054", "-d", "--caname", "rootca3"})
if err == nil {
t.Errorf("Should have failed, rootca3 does not exist on server")
}

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

func TestCleanUp(t *testing.T) {
os.Remove("../../testdata/cert.pem")
os.Remove("../../testdata/key.pem")
os.Remove("../../testdata/ca-cert.pem")
os.Remove("../../testdata/ca-key.pem")
os.Remove(testYaml)
os.Remove(fabricCADB)
os.RemoveAll(mspDir)
cleanMultiCADir()
}

func cleanMultiCADir() {
caFolder := "../../testdata/ca/rootca"
nestedFolders := []string{"ca1", "ca2"}
removeFiles := []string{"ec.pem", "ec-key.pem", "fabric-ca-server.db", "fabric-ca2-server.db", "ca-chain.pem"}

for _, nestedFolder := range nestedFolders {
path := filepath.Join(caFolder, nestedFolder)
for _, file := range removeFiles {
os.Remove(filepath.Join(path, file))
}
}
}

func TestRegisterWithoutEnroll(t *testing.T) {
Expand All @@ -595,8 +640,7 @@ func getServer() *lib.Server {
HomeDir: ".",
Config: getServerConfig(),
CA: lib.CA{
HomeDir: ".",
Config: getCAConfig(),
Config: getCAConfig(),
},
}
}
Expand Down Expand Up @@ -624,7 +668,10 @@ func getSerialAKIByID(id string) (serial, aki string, err error) {
testdb, _, _ := dbutil.NewUserRegistrySQLLite3(srv.CA.Config.DB.Datasource)
acc := lib.NewCertDBAccessor(testdb)

certs, _ := acc.GetCertificatesByID(id)
certs, err := acc.GetCertificatesByID(id)
if err != nil {
return "", "", err
}

block, _ := pem.Decode([]byte(certs[0].PEM))
if block == nil {
Expand Down
27 changes: 5 additions & 22 deletions cmd/fabric-ca-client/revoke.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@ import (
"github.com/cloudflare/cfssl/log"
"github.com/hyperledger/fabric-ca/api"
"github.com/hyperledger/fabric-ca/lib"
"github.com/hyperledger/fabric-ca/util"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

var errInput = errors.New("Invalid usage; either --eid or both --serial and --aki are required")
Expand Down Expand Up @@ -62,11 +60,6 @@ var revokeCmd = &cobra.Command{

func init() {
rootCmd.AddCommand(revokeCmd)
revokeFlags := revokeCmd.Flags()
util.FlagString(revokeFlags, "eid", "e", "", "Enrollment ID (Optional)")
util.FlagString(revokeFlags, "serial", "s", "", "Serial Number")
util.FlagString(revokeFlags, "aki", "a", "", "AKI")
util.FlagString(revokeFlags, "reason", "r", "", "Reason for revoking")
}

// The client revoke main logic
Expand All @@ -75,10 +68,6 @@ func runRevoke(cmd *cobra.Command) error {

var err error

enrollmentID := viper.GetString("eid")
serial := viper.GetString("serial")
aki := viper.GetString("aki")

client := lib.Client{
HomeDir: filepath.Dir(cfgFileName),
Config: clientCfg,
Expand All @@ -94,23 +83,17 @@ func runRevoke(cmd *cobra.Command) error {
// specified OR enrollment ID must be specified, else return an error.
// Note that all three can be specified, in which case server will revoke
// certificate associated with the specified aki, serial number.
if (enrollmentID == "") && (aki == "" || serial == "") {
if (clientCfg.Revoke.Name == "") && (clientCfg.Revoke.AKI == "" || clientCfg.Revoke.Serial == "") {
cmd.Usage()
return errInput
}

reasonInput := viper.GetString("reason")
var reason int
if reasonInput != "" {
reason = util.RevocationReasonCodes[reasonInput]
}

err = id.Revoke(
&api.RevocationRequest{
Name: enrollmentID,
Serial: serial,
AKI: aki,
Reason: reason,
Name: clientCfg.Revoke.Name,
Serial: clientCfg.Revoke.Serial,
AKI: clientCfg.Revoke.AKI,
Reason: clientCfg.Revoke.Reason,
})

if err == nil {
Expand Down
4 changes: 3 additions & 1 deletion cmd/fabric-ca-server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,8 @@ ldap:
# The URL of the LDAP server
url: ldap://<adminDN>:<adminPassword>@<host>:<port>/<base>
tls:
certfiles: ldap-server-cert.pem # Comma Separated (e.g. root.pem, root2.pem)
certfiles:
- ldap-server-cert.pem
client:
certfile: ldap-client-cert.pem
keyfile: ldap-client-key.pem
Expand Down Expand Up @@ -320,6 +321,7 @@ func configInit() (err error) {
"tls.clientauth.certfiles",
"cafiles",
"db.tls.certfiles",
"ldap.tls.certfiles",
}
err = util.ViperUnmarshal(serverCfg, sliceFields, viper.GetViper())
if err != nil {
Expand Down
2 changes: 0 additions & 2 deletions cmd/fabric-ca-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ var (
return err
}
cmd.SilenceUsage = true
cmd.SilenceErrors = true
util.CmdRunBegin()
return nil
},
Expand All @@ -57,7 +56,6 @@ func init() {
// Set specific global flags used by all commands
pflags := rootCmd.PersistentFlags()
pflags.StringVarP(&cfgFileName, "config", "c", cfg, "Configuration file")
util.FlagString(pflags, "url", "u", "", "URL of the parent fabric-ca-server")
util.FlagString(pflags, "boot", "b", "",
"The user:pass for bootstrap admin which is required to build default config file")

Expand Down
Loading

0 comments on commit c131944

Please sign in to comment.