From c426926b43a7ee848f3b17ecd46f77b74b5ce67c Mon Sep 17 00:00:00 2001 From: Divyank Katira Date: Fri, 24 Mar 2017 13:32:01 -0400 Subject: [PATCH] Added user registration and revocation functionality Change-Id: I35fc09db4b7c79c8a521b48441647f30e4e55c96 Signed-off-by: Divyank Katira --- fabric-ca-client/fabricca.go | 127 +++++++++++++++++++++++++++++ fabric-ca-client/fabricca_test.go | 83 +++++++++++++++++++ fabric-ca-client/mocks/mockkey.go | 46 +++++++++++ test/integration/fabric_ca_test.go | 120 +++++++++++++++++---------- 4 files changed, 334 insertions(+), 42 deletions(-) create mode 100644 fabric-ca-client/mocks/mockkey.go diff --git a/fabric-ca-client/fabricca.go b/fabric-ca-client/fabricca.go index 4caa355fd9..0a6149c1e0 100644 --- a/fabric-ca-client/fabricca.go +++ b/fabric-ca-client/fabricca.go @@ -20,12 +20,14 @@ limitations under the License. package fabricca import ( + "encoding/base64" "fmt" "os" "github.com/hyperledger/fabric-ca/api" fabric_ca "github.com/hyperledger/fabric-ca/lib" "github.com/hyperledger/fabric-sdk-go/config" + fabricclient "github.com/hyperledger/fabric-sdk-go/fabric-client" "github.com/op/go-logging" ) @@ -35,12 +37,47 @@ var logger = logging.MustGetLogger("fabric_sdk_go") // Services ... type Services interface { Enroll(enrollmentID string, enrollmentSecret string) ([]byte, []byte, error) + Register(registrar fabricclient.User, request *RegistrationRequest) (string, error) + Revoke(registrar fabricclient.User, request *RevocationRequest) error } type services struct { fabricCAClient *fabric_ca.Client } +type RegistrationRequest struct { + // Name is the unique name of the identity + Name string + // Type of identity being registered (e.g. "peer, app, user") + Type string + // MaxEnrollments is the number of times the secret can be reused to enroll. + // if omitted, this defaults to max_enrollments configured on the server + MaxEnrollments int + // The identity's affiliation e.g. org1.department1 + Affiliation string + // Optional attributes associated with this identity + Attributes []Attribute +} + +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 + // Serial number of the certificate to be revoked + // If this is omitted, then Name must be specified + Serial string + // AKI (Authority Key Identifier) of the certificate to be revoked + AKI string + // 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 +} + +type Attribute struct { + Key string + Value string +} + // NewFabricCAClient ... /** * @param {string} clientConfigFile for fabric-ca services" @@ -89,3 +126,93 @@ func (fabricCAServices *services) Enroll(enrollmentID string, enrollmentSecret s } return id.GetECert().Key(), id.GetECert().Cert(), nil } + +// Register a User with the Fabric CA +// @param {User} registrar The User that is initiating the registration +// @param {RegistrationRequest} request Registration Request +// @returns {string} Enrolment Secret +// @returns {error} Error +func (fabricCAServices *services) Register(registrar fabricclient.User, + request *RegistrationRequest) (string, error) { + // Validate registration request + if request == nil { + return "", fmt.Errorf("Registration request cannot be nil") + } + // Create request signing identity + identity, err := fabricCAServices.createSigningIdentity(registrar) + if err != nil { + return "", fmt.Errorf("Error creating signing identity: %s", err.Error()) + } + // Contruct request for Fabric CA client + var attributes []api.Attribute + for i, _ := range request.Attributes { + attributes = append(attributes, api.Attribute{Name: request. + Attributes[i].Key, Value: request.Attributes[i].Value}) + } + var req = api.RegistrationRequest{ + Name: request.Name, + Type: request.Type, + MaxEnrollments: request.MaxEnrollments, + Affiliation: request.Affiliation, + Attributes: attributes} + // Make registration request + response, err := identity.Register(&req) + if err != nil { + return "", fmt.Errorf("Error Registering User: %s", err.Error()) + } + // Decode enrolment secret + secret, err := base64.StdEncoding.DecodeString(response.Secret) + if err != nil { + return "", fmt.Errorf("Error decoding enrolment secret: %s", err.Error()) + } + + return string(secret), nil +} + +// Revoke a User with the Fabric CA +// @param {User} registrar The User that is initiating the revocation +// @param {RevocationRequest} request Revocation Request +// @returns {error} Error +func (fabricCAServices *services) Revoke(registrar fabricclient.User, + request *RevocationRequest) error { + // Validate revocation request + if request == nil { + return fmt.Errorf("Revocation request cannot be nil") + } + // Create request signing identity + identity, err := fabricCAServices.createSigningIdentity(registrar) + if err != nil { + return fmt.Errorf("Error creating signing identity: %s", err.Error()) + } + // Create revocation request + var req = api.RevocationRequest{ + Name: request.Name, + Serial: request.Serial, + AKI: request.AKI, + Reason: request.Reason} + return identity.Revoke(&req) +} + +// createSigningIdentity creates an identity to sign Fabric CA requests with +func (fabricCAServices *services) createSigningIdentity(user fabricclient. + User) (*fabric_ca.Identity, error) { + // Validate user + if user == nil { + return nil, fmt.Errorf("Valid user required to create signing identity") + } + // Validate enrolment information + cert := user.GetEnrollmentCertificate() + key := user.GetPrivateKey() + if key == nil || cert == nil { + return nil, fmt.Errorf( + "Unable to read user enrolment information to create signing identity") + } + // TODO: Right now this reads the key from a default BCCSP implementation using the SKI + // this method signature will change to accepting a BCCSP key soon. + // Track changes here: https://gerrit.hyperledger.org/r/#/c/6727/ + ski := key.SKI() + if ski == nil { + return nil, fmt.Errorf("Unable to read private key SKI") + } + return fabricCAServices.fabricCAClient.NewIdentity(ski, cert) +} diff --git a/fabric-ca-client/fabricca_test.go b/fabric-ca-client/fabricca_test.go index 86c1b79c97..a197577ba3 100644 --- a/fabric-ca-client/fabricca_test.go +++ b/fabric-ca-client/fabricca_test.go @@ -20,7 +20,11 @@ limitations under the License. package fabricca import ( + "io/ioutil" "testing" + + "github.com/hyperledger/fabric-sdk-go/fabric-ca-client/mocks" + "github.com/hyperledger/fabric-sdk-go/fabric-client" ) func TestEnrollWithMissingParameters(t *testing.T) { @@ -43,3 +47,82 @@ func TestEnrollWithMissingParameters(t *testing.T) { t.Fatalf("Enroll didn't return right error") } } + +func TestRegister(t *testing.T) { + fabricCAClient, err := NewFabricCAClient() + if err != nil { + t.Fatalf("NewFabricCAClient returned error: %v", err) + } + mockKey := &mocks.MockKey{} + user := fabricclient.NewUser("test") + // Register with nil request + _, err = fabricCAClient.Register(user, nil) + if err == nil { + t.Fatalf("Expected error with nil request") + } + //Register with nil user + _, err = fabricCAClient.Register(nil, &RegistrationRequest{}) + if err == nil { + t.Fatalf("Expected error with nil user") + } + // Register with nil user cert and key + _, err = fabricCAClient.Register(user, &RegistrationRequest{}) + if err == nil { + t.Fatalf("Expected error without user enrolment information") + } + user.SetEnrollmentCertificate(readCert(t)) + user.SetPrivateKey(mockKey) + // Register without registration name paramter + _, err = fabricCAClient.Register(user, &RegistrationRequest{}) + if err.Error() != "Error Registering User: Register was called without a Name set" { + t.Fatalf("Expected error without registration information. Got: %s", err.Error()) + } + // Register without registration affiliation paramter + _, err = fabricCAClient.Register(user, &RegistrationRequest{Name: "test"}) + if err.Error() != "Error Registering User: Registration request does not have an affiliation" { + t.Fatalf("Expected error without registration information. Got: %s", err.Error()) + } + // Register with valid request + var attributes []Attribute + attributes = append(attributes, Attribute{Key: "test1", Value: "test2"}) + attributes = append(attributes, Attribute{Key: "test2", Value: "test3"}) + _, err = fabricCAClient.Register(user, &RegistrationRequest{Name: "test", + Affiliation: "test", Attributes: attributes}) + if err == nil { + t.Fatalf("Expected PEM decoding error with test cert") + } +} + +func TestRevoke(t *testing.T) { + fabricCAClient, err := NewFabricCAClient() + if err != nil { + t.Fatalf("NewFabricCAClient returned error: %v", err) + } + mockKey := &mocks.MockKey{} + user := fabricclient.NewUser("test") + // Revoke with nil request + err = fabricCAClient.Revoke(user, nil) + if err == nil { + t.Fatalf("Expected error with nil request") + } + //Revoke with nil user + err = fabricCAClient.Revoke(nil, &RevocationRequest{}) + if err == nil { + t.Fatalf("Expected error with nil user") + } + user.SetEnrollmentCertificate(readCert(t)) + user.SetPrivateKey(mockKey) + err = fabricCAClient.Revoke(user, &RevocationRequest{}) + if err == nil { + t.Fatalf("Expected decoding error with test cert") + } +} + +// Reads a random cert for testing +func readCert(t *testing.T) []byte { + cert, err := ioutil.ReadFile("../test/fixtures/root.pem") + if err != nil { + t.Fatalf("Error reading cert: %s", err.Error()) + } + return cert +} diff --git a/fabric-ca-client/mocks/mockkey.go b/fabric-ca-client/mocks/mockkey.go new file mode 100644 index 0000000000..3be3350a69 --- /dev/null +++ b/fabric-ca-client/mocks/mockkey.go @@ -0,0 +1,46 @@ +/* +Copyright SecureKey Technologies Inc. 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 mocks + +import "github.com/hyperledger/fabric/bccsp" + +// MockKey mocks BCCSP key +type MockKey struct { +} + +func (m *MockKey) Bytes() ([]byte, error) { + return []byte("Not implemented"), nil +} + +func (m *MockKey) SKI() []byte { + return []byte("Not implemented") +} + +func (m *MockKey) Symmetric() bool { + return false +} + +func (m *MockKey) Private() bool { + return true +} + +func (m *MockKey) PublicKey() (bccsp.Key, error) { + return m, nil +} diff --git a/test/integration/fabric_ca_test.go b/test/integration/fabric_ca_test.go index a603fba501..5731f03097 100644 --- a/test/integration/fabric_ca_test.go +++ b/test/integration/fabric_ca_test.go @@ -22,7 +22,11 @@ package integration import ( "crypto/x509" "encoding/pem" + "fmt" + "math/rand" + "strconv" "testing" + "time" config "github.com/hyperledger/fabric-sdk-go/config" fabric_client "github.com/hyperledger/fabric-sdk-go/fabric-client" @@ -33,11 +37,9 @@ import ( fabric_ca_client "github.com/hyperledger/fabric-sdk-go/fabric-ca-client" ) -// this test uses the FabricCAServices to enroll a user, and -// saves the enrollment materials into a key value store. -// then uses the Client class to load the member from the -// key value store -func TestEnroll(t *testing.T) { +// This test loads/enrols an admin user +// Using the admin, it registers, enrols, and revokes a test user +func TestRegisterEnrollRevoke(t *testing.T) { InitConfigForFabricCA() client := fabric_client.NewClient() @@ -69,55 +71,89 @@ func TestEnroll(t *testing.T) { if err != nil { t.Fatalf("NewFabricCAClient return error: %v", err) } - key, cert, err := fabricCAClient.Enroll("testUser2", "user2") - if err != nil { - t.Fatalf("Enroll return error: %v", err) - } - if key == nil { - t.Fatalf("private key return from Enroll is nil") - } - if cert == nil { - t.Fatalf("cert return from Enroll is nil") - } - certPem, _ := pem.Decode(cert) + // Admin user is used to register, enrol and revoke a test user + user, err := client.GetUserContext("admin2") if err != nil { - t.Fatalf("pem Decode return error: %v", err) - } - - cert509, err := x509.ParseCertificate(certPem.Bytes) - if err != nil { - t.Fatalf("x509 ParseCertificate return error: %v", err) + t.Fatalf("client.GetUserContext return error: %v", err) } - if cert509.Subject.CommonName != "testUser2" { - t.Fatalf("CommonName in x509 cert is not the enrollmentID") + if user == nil { + key, cert, err := fabricCAClient.Enroll("admin2", "adminpw2") + if err != nil { + t.Fatalf("Enroll return error: %v", err) + } + if key == nil { + t.Fatalf("private key return from Enroll is nil") + } + if cert == nil { + t.Fatalf("cert return from Enroll is nil") + } + + certPem, _ := pem.Decode(cert) + if err != nil { + t.Fatalf("pem Decode return error: %v", err) + } + + cert509, err := x509.ParseCertificate(certPem.Bytes) + if err != nil { + t.Fatalf("x509 ParseCertificate return error: %v", err) + } + if cert509.Subject.CommonName != "admin2" { + t.Fatalf("CommonName in x509 cert is not the enrollmentID") + } + + keyPem, _ := pem.Decode(key) + if err != nil { + t.Fatalf("pem Decode return error: %v", err) + } + user := fabric_client.NewUser("admin2") + k, err := client.GetCryptoSuite().KeyImport(keyPem.Bytes, &bccsp.ECDSAPrivateKeyImportOpts{Temporary: false}) + if err != nil { + t.Fatalf("KeyImport return error: %v", err) + } + user.SetPrivateKey(k) + user.SetEnrollmentCertificate(cert) + err = client.SetUserContext(user, false) + if err != nil { + t.Fatalf("client.SetUserContext return error: %v", err) + } + user, err = client.GetUserContext("admin2") + if err != nil { + t.Fatalf("client.GetUserContext return error: %v", err) + } + if user == nil { + t.Fatalf("client.GetUserContext return nil") + } } - keyPem, _ := pem.Decode(key) + // Register a random user + userName := createRandomName() + registerRequest := fabric_ca_client.RegistrationRequest{Name: userName, Type: "user", Affiliation: "org1.department1"} + enrolmentSecret, err := fabricCAClient.Register(user, ®isterRequest) if err != nil { - t.Fatalf("pem Decode return error: %v", err) + fmt.Printf("Error from Register: %s", err) + t.FailNow() } - user := fabric_client.NewUser("testUser2") - k, err := client.GetCryptoSuite().KeyImport(keyPem.Bytes, &bccsp.ECDSAPrivateKeyImportOpts{Temporary: false}) + fmt.Printf("Registered User: %s, Secret: %s\n", userName, enrolmentSecret) + // Enrol the previously registered user + _, _, err = fabricCAClient.Enroll(userName, enrolmentSecret) if err != nil { - t.Fatalf("KeyImport return error: %v", err) + t.Fatalf("Error enroling user: %s", err.Error()) } - user.SetPrivateKey(k) - user.SetEnrollmentCertificate(cert) - err = client.SetUserContext(user, false) + + revokeRequest := fabric_ca_client.RevocationRequest{Name: userName} + err = fabricCAClient.Revoke(user, &revokeRequest) if err != nil { - t.Fatalf("client.SetUserContext return error: %v", err) + fmt.Printf("Error from Revoke: %s", err) + t.FailNow() } - user, err = client.GetUserContext("testUser2") - if err != nil { - t.Fatalf("client.GetUserContext return error: %v", err) - } - if user == nil { - t.Fatalf("client.GetUserContext return nil") - } - } func InitConfigForFabricCA() { - config.InitConfig("./test_resources/config/config_test.yaml") + config.InitConfig("../fixtures/config/config_test.yaml") +} + +func createRandomName() string { + rand.Seed(time.Now().UnixNano()) + return "user" + strconv.Itoa(rand.Intn(5000)) }