diff --git a/api/client.go b/api/client.go index b100238b5..1103c3798 100644 --- a/api/client.go +++ b/api/client.go @@ -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 @@ -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. @@ -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 @@ -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 @@ -120,6 +128,8 @@ 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 @@ -127,6 +137,11 @@ 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"` diff --git a/api/net.go b/api/net.go index dcb7c9066..20c5d1ef4 100644 --- a/api/net.go +++ b/api/net.go @@ -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 diff --git a/cmd/fabric-ca-client/getcacert.go b/cmd/fabric-ca-client/getcacert.go index 94a75440e..9c4cd8c8f 100644 --- a/cmd/fabric-ca-client/getcacert.go +++ b/cmd/fabric-ca-client/getcacert.go @@ -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" @@ -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 } diff --git a/cmd/fabric-ca-client/main.go b/cmd/fabric-ca-client/main.go index 4dc86f5bb..b3aaf06d9 100644 --- a/cmd/fabric-ca-client/main.go +++ b/cmd/fabric-ca-client/main.go @@ -36,7 +36,6 @@ var rootCmd = &cobra.Command{ util.CmdRunBegin() cmd.SilenceUsage = true - cmd.SilenceErrors = true return nil }, @@ -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) } } diff --git a/cmd/fabric-ca-client/main_test.go b/cmd/fabric-ca-client/main_test.go index 4fd858805..f6412170c 100644 --- a/cmd/fabric-ca-client/main_test.go +++ b/cmd/fabric-ca-client/main_test.go @@ -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, @@ -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 @@ -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 @@ -446,6 +447,7 @@ func testRevoke(t *testing.T) { } os.RemoveAll(filepath.Dir(defYaml)) + } // TestBogus tests a negative test case @@ -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) { @@ -595,8 +640,7 @@ func getServer() *lib.Server { HomeDir: ".", Config: getServerConfig(), CA: lib.CA{ - HomeDir: ".", - Config: getCAConfig(), + Config: getCAConfig(), }, } } @@ -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 { diff --git a/cmd/fabric-ca-client/revoke.go b/cmd/fabric-ca-client/revoke.go index 32a53e2f9..1d0d874dd 100644 --- a/cmd/fabric-ca-client/revoke.go +++ b/cmd/fabric-ca-client/revoke.go @@ -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") @@ -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 @@ -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, @@ -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 { diff --git a/cmd/fabric-ca-server/config.go b/cmd/fabric-ca-server/config.go index 938a7589a..aee0c6b34 100644 --- a/cmd/fabric-ca-server/config.go +++ b/cmd/fabric-ca-server/config.go @@ -188,7 +188,8 @@ ldap: # The URL of the LDAP server url: ldap://:@:/ 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 @@ -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 { diff --git a/cmd/fabric-ca-server/main.go b/cmd/fabric-ca-server/main.go index 2f41ac789..048852a5d 100644 --- a/cmd/fabric-ca-server/main.go +++ b/cmd/fabric-ca-server/main.go @@ -38,7 +38,6 @@ var ( return err } cmd.SilenceUsage = true - cmd.SilenceErrors = true util.CmdRunBegin() return nil }, @@ -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") diff --git a/cmd/fabric-ca-server/main_test.go b/cmd/fabric-ca-server/main_test.go index 746589926..f455dd514 100644 --- a/cmd/fabric-ca-server/main_test.go +++ b/cmd/fabric-ca-server/main_test.go @@ -20,6 +20,7 @@ import ( "fmt" "io/ioutil" "os" + "path/filepath" "regexp" "testing" @@ -207,12 +208,12 @@ func TestDBLocation(t *testing.T) { func TestMultiCA(t *testing.T) { blockingStart = false - err := RunMain([]string{cmdName, "start", "-d", "-p", "7055", "-c", "../../testdata/test.yaml", "-b", "user:pass", "--cafiles", "ca/ca1/fabric-ca-server-config.yaml", "--cafiles", "ca/ca2/fabric-ca-server-config.yaml"}) + err := RunMain([]string{cmdName, "start", "-d", "-p", "7055", "-c", "../../testdata/test.yaml", "-b", "user:pass", "--cafiles", "ca/rootca/ca1/fabric-ca-server-config.yaml", "--cafiles", "ca/rootca/ca2/fabric-ca-server-config.yaml"}) if err != nil { t.Error("Failed to start server with multiple CAs using the --cafiles flag from command line: ", err) } - if !util.FileExists("../../testdata/ca/ca2/fabric-ca2-server.db") { + if !util.FileExists("../../testdata/ca/rootca/ca2/fabric-ca2-server.db") { t.Error("Failed to create 2 CA instances") } @@ -248,11 +249,16 @@ func TestClean(t *testing.T) { } func cleanUpMultiCAFiles() { + caFolder := "../../testdata/ca/rootca" + nestedFolders := []string{"ca1", "ca2"} + removeFiles := []string{"ca-cert.pem", "ca-key.pem", "fabric-ca-server.db", "fabric-ca2-server.db"} + + for _, nestedFolder := range nestedFolders { + path := filepath.Join(caFolder, nestedFolder) + for _, file := range removeFiles { + os.Remove(filepath.Join(path, file)) + } + } + os.Remove("../../testdata/test.yaml") - os.Remove("../../testdata/ca/ca1/ca-cert.pem") - os.Remove("../../testdata/ca/ca1/ca-key.pem") - os.Remove("../../testdata/ca/ca1/fabric-ca-server.db") - os.Remove("../../testdata/ca/ca2/ca-cert.pem") - os.Remove("../../testdata/ca/ca2/ca-key.pem") - os.Remove("../../testdata/ca/ca2/fabric-ca2-server.db") } diff --git a/lib/ca.go b/lib/ca.go index 94e5a1913..26f02f81a 100644 --- a/lib/ca.go +++ b/lib/ca.go @@ -177,8 +177,8 @@ func (ca *CA) initKeyMaterial(renew bool) error { // Get the CA certificate and key for this CA func (ca *CA) getCACertAndKey() (cert, key []byte, err error) { - log.Debugf("Getting CA cert and key; parent server URL is '%s'", ca.Config.ParentServerURL) - if ca.Config.ParentServerURL != "" { + log.Debugf("Getting CA cert and key; parent server URL is '%s'", ca.Config.ParentServer.URL) + if ca.Config.ParentServer.URL != "" { // This is an intermediate CA, so call the parent fabric-ca-server // to get the key and cert clientCfg := ca.Config.Client @@ -196,7 +196,10 @@ func (ca *CA) getCACertAndKey() (cert, key []byte, err error) { } log.Debugf("Intermediate enrollment request: %v", clientCfg.Enrollment) var resp *EnrollmentResponse - resp, err = clientCfg.Enroll(ca.Config.ParentServerURL, ca.HomeDir) + if ca.Config.ParentServer.CAName == "" { + ca.Config.ParentServer.CAName = ca.server.CA.Config.CA.Name + } + resp, err = clientCfg.Enroll(ca.Config.ParentServer.URL, ca.HomeDir) if err != nil { return nil, nil, err } @@ -264,7 +267,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.ParentServerURL == "" { + if ca.Config.ParentServer.URL == "" { return util.ReadFile(certAuth.Certfile) } // If this is an intermediate CA but the ca.Chainfile doesn't exist, @@ -418,7 +421,7 @@ func (ca *CA) initEnrollmentSigner() (err error) { } // Make sure the policy reflects the new remote - ParentServerURL := ca.Config.ParentServerURL + ParentServerURL := ca.Config.ParentServer.URL if ParentServerURL != "" { err = policy.OverrideRemotes(ParentServerURL) if err != nil { diff --git a/lib/caconfig.go b/lib/caconfig.go index e8e8d4c64..fd0ff7ebc 100644 --- a/lib/caconfig.go +++ b/lib/caconfig.go @@ -33,16 +33,16 @@ import ( // "help" - the help message to display on the command line; // "skip" - to skip the field. type CAConfig struct { - CSP *factory.FactoryOpts - CA CAInfo - ParentServerURL string `skip:"true"` - Signing *config.Signing - CSR csr.CertificateRequest - Registry CAConfigRegistry - Affiliations map[string]interface{} - LDAP ldap.Config - DB CAConfigDB - Client *ClientConfig + CSP *factory.FactoryOpts + CA CAInfo + ParentServer ParentServer + Signing *config.Signing + CSR csr.CertificateRequest + Registry CAConfigRegistry + Affiliations map[string]interface{} + LDAP ldap.Config + DB CAConfigDB + Client *ClientConfig } // CAInfo is the CA information on a fabric-ca-server @@ -76,6 +76,13 @@ type CAConfigIdentity struct { Attrs map[string]string } +// ParentServer contains URL for the parent server and the name of CA inside +// the server to connect to +type ParentServer struct { + URL string `opt:"u" help:"URL of the parent fabric-ca-server (e.g. http://:@
: maxEnrollments && maxEnrollments != 0) || (req.MaxEnrollments < 0) { return "", fmt.Errorf("Invalid max enrollment value specified, value must be equal to or less then %d", maxEnrollments) @@ -148,10 +150,10 @@ func (h *registerHandler) registerUserID(req *api.RegistrationRequestNet) (strin Type: req.Type, Affiliation: req.Affiliation, Attributes: req.Attributes, - MaxEnrollments: req.MaxEnrollments, + MaxEnrollments: maxEnrollments, } - registry := h.server.registry + registry := h.server.caMap[caname].registry _, err := registry.GetUser(req.Name, nil) if err == nil { @@ -166,10 +168,10 @@ func (h *registerHandler) registerUserID(req *api.RegistrationRequestNet) (strin return req.Secret, nil } -func (h *registerHandler) isValidAffiliation(affiliation string) error { +func (h *registerHandler) isValidAffiliation(affiliation string, caname string) error { log.Debug("Validating affiliation: " + affiliation) - _, err := h.server.registry.GetAffiliation(affiliation) + _, err := h.server.caMap[caname].registry.GetAffiliation(affiliation) if err != nil { return fmt.Errorf("Failed getting affiliation '%s': %s", affiliation, err) } @@ -183,10 +185,10 @@ func (h *registerHandler) requireAffiliation(idType string) bool { return true } -func (h *registerHandler) canRegister(registrar string, userType string) error { - log.Debugf("canRegister - Check to see if identity %s can register", registrar) +func (h *registerHandler) canRegister(registrar string, userType string, caname string) error { + log.Debugf("canRegister - Check to see if user %s can register", registrar) - user, err := h.server.registry.GetUser(registrar, nil) + user, err := h.server.caMap[caname].registry.GetUser(registrar, nil) if err != nil { return fmt.Errorf("Registrar does not exist: %s", err) } diff --git a/lib/serverrevoke.go b/lib/serverrevoke.go index b6a446668..b08a3d85e 100644 --- a/lib/serverrevoke.go +++ b/lib/serverrevoke.go @@ -46,7 +46,6 @@ type revokeHandler struct { // Handle an revoke request func (h *revokeHandler) Handle(w http.ResponseWriter, r *http.Request) error { - log.Debug("Revoke request received") authHdr := r.Header.Get("authorization") @@ -60,7 +59,18 @@ func (h *revokeHandler) Handle(w http.ResponseWriter, r *http.Request) error { } r.Body.Close() - cert, err := util.VerifyToken(h.server.csp, authHdr, body) + // Parse revoke request body + var req api.RevocationRequestNet + err = json.Unmarshal(body, &req) + if err != nil { + return badRequest(w, err) + } + + log.Debugf("Revoke request: %+v", req) + + caname := r.Header.Get(caHdrName) + + cert, err := util.VerifyToken(h.server.caMap[caname].csp, authHdr, body) if err != nil { return authErr(w, err) } @@ -69,25 +79,17 @@ func (h *revokeHandler) Handle(w http.ResponseWriter, r *http.Request) error { // to revoke a certificate. This attribute comes from the user registry, which // is either in the DB if LDAP is not configured, or comes from LDAP if LDAP is // configured. - err = h.server.userHasAttribute(cert.Subject.CommonName, "hf.Revoker") + err = h.server.caMap[caname].userHasAttribute(cert.Subject.CommonName, "hf.Revoker") if err != nil { return authErr(w, err) } - // Parse revoke request body - var req api.RevocationRequestNet - err = json.Unmarshal(body, &req) - if err != nil { - return badRequest(w, err) - } - - log.Debugf("Revoke request: %+v", req) - req.AKI = strings.TrimLeft(strings.ToLower(req.AKI), "0") req.Serial = strings.TrimLeft(strings.ToLower(req.Serial), "0") - certDBAccessor := h.server.certDBAccessor - registry := h.server.registry + certDBAccessor := h.server.caMap[caname].certDBAccessor + registry := h.server.caMap[caname].registry + reason := util.RevocationReasonCodes[req.Reason] if req.Serial != "" && req.AKI != "" { certificate, err := certDBAccessor.GetCertificateWithID(req.Serial, req.AKI) @@ -116,7 +118,7 @@ func (h *revokeHandler) Handle(w http.ResponseWriter, r *http.Request) error { return authErr(w, err2) } - err = certDBAccessor.RevokeCertificate(req.Serial, req.AKI, req.Reason) + err = certDBAccessor.RevokeCertificate(req.Serial, req.AKI, reason) if err != nil { msg := fmt.Sprintf("Failed to revoke certificate: %s", err) log.Error(msg) @@ -155,7 +157,7 @@ func (h *revokeHandler) Handle(w http.ResponseWriter, r *http.Request) error { } var recs []CertRecord - recs, err = certDBAccessor.RevokeCertificatesByID(req.Name, req.Reason) + recs, err = certDBAccessor.RevokeCertificatesByID(req.Name, reason) if err != nil { log.Warningf("No certificates were revoked for '%s' but the ID was disabled: %s", req.Name, err) return dbErr(w, err) diff --git a/lib/servertcert.go b/lib/servertcert.go index 0d393f2a2..705120bf2 100644 --- a/lib/servertcert.go +++ b/lib/servertcert.go @@ -40,31 +40,11 @@ type tcertHandler struct { // newTCertHandler is constructor for tcert handler func newTCertHandler(server *Server) (h http.Handler, err error) { - handler, err := initTCertHandler(server) - if err != nil { - return nil, fmt.Errorf("Failed to initialize TCert handler: %s", err) - } - return handler, nil -} - -func initTCertHandler(server *Server) (h http.Handler, err error) { - log.Debug("Initializing TCert handler") - keyfile := server.CA.Config.CA.Keyfile - certfile := server.CA.Config.CA.Certfile - mgr, err := tcert.LoadMgr(keyfile, certfile) - if err != nil { - return nil, err - } - // FIXME: The root prekey must be stored persistently in DB and retrieved here if not found - rootKey, err := genRootKey(server.csp) - if err != nil { - return nil, err - } - keyTree := tcert.NewKeyTree(server.csp, rootKey) handler := &cfsslapi.HTTPHandler{ - Handler: &tcertHandler{server: server, mgr: mgr, keyTree: keyTree}, + Handler: &tcertHandler{server: server}, Methods: []string{"POST"}, } + return handler, nil } @@ -90,6 +70,13 @@ func (h *tcertHandler) handle(w http.ResponseWriter, r *http.Request) error { return err } + caname := r.Header.Get(caHdrName) + + err = h.initTCertHandler(h.server, caname) + if err != nil { + return fmt.Errorf("Failed to initialize TCert handler: %s", err) + } + // Get an X509 certificate from the authorization header associated with the caller cert, err := getCertFromAuthHdr(r) if err != nil { @@ -98,7 +85,7 @@ func (h *tcertHandler) handle(w http.ResponseWriter, r *http.Request) error { // Get the user's attribute values and affiliation path id := tcert.GetEnrollmentIDFromCert(cert) - attrs, affiliationPath, err := h.getUserInfo(id, req.AttrNames) + attrs, affiliationPath, err := h.getUserInfo(id, req.AttrNames, caname) if err != nil { return err } @@ -121,6 +108,7 @@ func (h *tcertHandler) handle(w http.ResponseWriter, r *http.Request) error { ValidityPeriod: req.ValidityPeriod, PreKey: prekeyStr, } + resp, err := h.mgr.GetBatch(tcertReq, cert) if err != nil { return err @@ -134,9 +122,28 @@ func (h *tcertHandler) handle(w http.ResponseWriter, r *http.Request) error { } +func (h *tcertHandler) initTCertHandler(server *Server, caname string) (err error) { + log.Debug("Initializing TCert handler") + keyfile := server.caMap[caname].Config.CA.Keyfile + certfile := server.caMap[caname].Config.CA.Certfile + + h.mgr, err = tcert.LoadMgr(keyfile, certfile) + if err != nil { + return err + } + // FIXME: The root prekey must be stored persistently in DB and retrieved here if not found + rootKey, err := genRootKey(server.caMap[caname].csp) + if err != nil { + return err + } + h.keyTree = tcert.NewKeyTree(server.caMap[caname].csp, rootKey) + + return nil +} + // getUserinfo returns the users requested attribute values and user's affiliation path -func (h *tcertHandler) getUserInfo(id string, attrNames []string) ([]tcert.Attribute, []string, error) { - user, err := h.server.registry.GetUser(id, attrNames) +func (h *tcertHandler) getUserInfo(id string, attrNames []string, caname string) ([]tcert.Attribute, []string, error) { + user, err := h.server.caMap[caname].registry.GetUser(id, attrNames) if err != nil { return nil, nil, err } diff --git a/testdata/ca/intermediateca/ca1/fabric-ca-server-config.yaml b/testdata/ca/intermediateca/ca1/fabric-ca-server-config.yaml new file mode 100644 index 000000000..656ba5b66 --- /dev/null +++ b/testdata/ca/intermediateca/ca1/fabric-ca-server-config.yaml @@ -0,0 +1,19 @@ +# ############################################################################# +# The CA section contains information specific to this Certificate Authority. +# Minimally, the name must be unique for all CAs serviced by the same server. +# Additionally, you may specify any of the settings that are defined in the +# server's configuration file to override them with a value specific for this CA. +# For example, you should provide a different username and password for the +# bootstrap identity as found in the "identities" subsection of the "registry" section. + +# See the server's configuration file for comments on all settings. +# All settings pertaining to the server's listening endpoint are by definition +# server-specific and so will be ignored in a CA configuration file. +# ############################################################################# +ca: + # Name of this CA + name: ca1 + +parentserver: + url: http://admin:adminpw@localhost:7055 + caname: rootca2 diff --git a/testdata/ca/ca2/fabric-ca-server-config.yaml b/testdata/ca/intermediateca/ca2/fabric-ca-server-config.yaml similarity index 64% rename from testdata/ca/ca2/fabric-ca-server-config.yaml rename to testdata/ca/intermediateca/ca2/fabric-ca-server-config.yaml index b25f7517d..0cd1f0ff4 100644 --- a/testdata/ca/ca2/fabric-ca-server-config.yaml +++ b/testdata/ca/intermediateca/ca2/fabric-ca-server-config.yaml @@ -48,6 +48,7 @@ registry: hf.Registrar.DelegateRoles: "client,user,validator,auditor" hf.Revoker: true hf.IntermediateCA: true + ############################################################################# # Database section # Supported types are: "sqlite3", "postgres", and "mysql". @@ -69,25 +70,6 @@ db: certfile: db-client-cert.pem keyfile: db-client-key.pem -############################################################################# -# LDAP section -# If LDAP is enabled, the fabric-ca-server calls LDAP to: -# 1) authenticate enrollment ID and secret (i.e. username and password) -# for enrollment requests; -# 2) To retrieve identity attributes -############################################################################# -ldap: - # Enables or disables the LDAP client (default: false) - enabled: false - # The URL of the LDAP server - url: ldap://:@:/ - tls: - certfiles: - - ldap-server-cert.pem - client: - certfile: ldap-client-cert.pem - keyfile: ldap-client-key.pem - ############################################################################# # Affiliation section ############################################################################# @@ -97,47 +79,3 @@ affiliations: - department2 org2: - department1 - -############################################################################# -# Signing section -############################################################################# -signing: - profiles: - ca: - usage: - - cert sign - expiry: 8000h - caconstraint: - isca: true - default: - usage: - - cert sign - expiry: 8000h - -########################################################################### -# Certificate Signing Request section for generating the CA certificate -########################################################################### -csr: - cn: fabric-ca-server - names: - - C: US - ST: "North Carolina" - L: - O: Hyperledger - OU: Fabric - hosts: - - saads-mbp.raleigh.ibm.com - ca: - pathlen: - pathlenzero: - expiry: - -############################################################################# -# Crypto section configures the crypto primitives used for all -############################################################################# -crypto: - software: - hash_family: SHA2 - security_level: 256 - ephemeral: false - key_store_dir: keys diff --git a/testdata/ca/ca1/fabric-ca-server-config.yaml b/testdata/ca/rootca/ca1/fabric-ca-server-config.yaml similarity index 62% rename from testdata/ca/ca1/fabric-ca-server-config.yaml rename to testdata/ca/rootca/ca1/fabric-ca-server-config.yaml index 817a7c696..aa426faec 100644 --- a/testdata/ca/ca1/fabric-ca-server-config.yaml +++ b/testdata/ca/rootca/ca1/fabric-ca-server-config.yaml @@ -1,18 +1,16 @@ -# ############################################################################# -# The CA section contains information specific to this Certificate Authority. -# Minimally, the name must be unique for all CAs serviced by the same server. -# Additionally, you may specify any of the settings that are defined in the -# server's configuration file to override them with a value specific for this CA. -# For example, you should provide a different username and password for the -# bootstrap identity as found in the "identities" subsection of the "registry" section. - -# See the server's configuration file for comments on all settings. -# All settings pertaining to the server's listening endpoint are by definition -# server-specific and so will be ignored in a CA configuration file. -# ############################################################################# +############################################################################# +# 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: # Name of this CA - name: ca1 + name: rootca1 ############################################################################# # The registry section controls how the fabric-ca-server does two things: diff --git a/testdata/ca/rootca/ca2/fabric-ca-server-config.yaml b/testdata/ca/rootca/ca2/fabric-ca-server-config.yaml new file mode 100644 index 000000000..c4c36bc51 --- /dev/null +++ b/testdata/ca/rootca/ca2/fabric-ca-server-config.yaml @@ -0,0 +1,36 @@ +# ############################################################################# +# The CA section contains information specific to this Certificate Authority. +# Minimally, the name must be unique for all CAs serviced by the same server. +# Additionally, you may specify any of the settings that are defined in the +# server's configuration file to override them with a value specific for this CA. +# For example, you should provide a different username and password for the +# bootstrap identity as found in the "identities" subsection of the "registry" section. + +# See the server's configuration file for comments on all settings. +# All settings pertaining to the server's listening endpoint are by definition +# server-specific and so will be ignored in a CA configuration file. +# ############################################################################# +ca: + # Name of this CA + name: rootca2 + +############################################################################# +# Database section +# Supported types are: "sqlite3", "postgres", and "mysql". +# The datasource value depends on the type. +# If the type is "sqlite3", the datasource value is a file name to use +# as the database store. Since "sqlite3" is an embedded database, it +# may not be used if you want to run the fabric-ca-server in a cluster. +# To run the fabric-ca-server in a cluster, you must choose "postgres" +# or "mysql". +############################################################################# +db: + type: sqlite3 + datasource: fabric-ca2-server.db + tls: + enabled: false + certfiles: + - db-server-cert.pem + client: + certfile: db-client-cert.pem + keyfile: db-client-key.pem