diff --git a/pkg/fabric-client/credentialmgr/credentialmgr.go b/pkg/fabric-client/credentialmgr/credentialmgr.go index de64721dc0..c14445b56f 100644 --- a/pkg/fabric-client/credentialmgr/credentialmgr.go +++ b/pkg/fabric-client/credentialmgr/credentialmgr.go @@ -9,6 +9,7 @@ package credentialmgr import ( "fmt" "io/ioutil" + "os" "path/filepath" "strings" @@ -101,8 +102,7 @@ func (mgr *CredentialManager) GetSigningIdentity(userName string) (*apifabclient } if certBytes == nil { - certBytes, err = mgr.getStoredCertBytes(userName) - + certBytes, err = mgr.getCertBytesFromCertStore(userName) if err != nil { return nil, errors.WithMessage(err, "fetching cert from store failed") } @@ -119,7 +119,7 @@ func (mgr *CredentialManager) GetSigningIdentity(userName string) (*apifabclient } if privateKey == nil { - privateKey, err = mgr.getPivateKeyFromCert(userName, certBytes) + privateKey, err = mgr.getPrivateKeyFromCert(userName, certBytes) if err != nil { return nil, errors.Wrapf(err, "getting private key from cert failed") } @@ -172,9 +172,18 @@ func (mgr *CredentialManager) getEmbeddedPrivateKey(userName string) (apicryptos pemBytes = []byte(keyPem) } else if keyPath != "" { // Try importing from the Embedded Path - pemBytes, err = ioutil.ReadFile(keyPath) + _, err := os.Stat(keyPath) if err != nil { - return nil, errors.Wrapf(err, "reading private key from embedded path failed") + if !os.IsNotExist(err) { + return nil, errors.Wrapf(err, "OS stat embedded path failed") + } + // file doesn't exist, continue + } else { + // file exists, try to read it + pemBytes, err = ioutil.ReadFile(keyPath) + if err != nil { + return nil, errors.Wrapf(err, "reading private key from embedded path failed") + } } } @@ -193,7 +202,7 @@ func (mgr *CredentialManager) getEmbeddedPrivateKey(userName string) (apicryptos return privateKey, nil } -func (mgr *CredentialManager) getStoredPrivateKeyPem(userName string, ski []byte) ([]byte, error) { +func (mgr *CredentialManager) getPrivateKeyPemFromKeyStore(userName string, ski []byte) ([]byte, error) { if mgr.privKeyStore == nil { return nil, nil } @@ -204,9 +213,6 @@ func (mgr *CredentialManager) getStoredPrivateKeyPem(userName string, ski []byte SKI: ski, }) if err != nil { - if err == kvstore.ErrNotFound { - return nil, nil - } return nil, err } keyBytes, ok := key.([]byte) @@ -216,7 +222,7 @@ func (mgr *CredentialManager) getStoredPrivateKeyPem(userName string, ski []byte return keyBytes, nil } -func (mgr *CredentialManager) getStoredCertBytes(userName string) ([]byte, error) { +func (mgr *CredentialManager) getCertBytesFromCertStore(userName string) ([]byte, error) { if mgr.certStore == nil { return nil, nil } @@ -237,7 +243,7 @@ func (mgr *CredentialManager) getStoredCertBytes(userName string) ([]byte, error return certBytes, nil } -func (mgr *CredentialManager) getPivateKeyFromCert(userName string, cert []byte) (apicryptosuite.Key, error) { +func (mgr *CredentialManager) getPrivateKeyFromCert(userName string, cert []byte) (apicryptosuite.Key, error) { if cert == nil { return nil, errors.New("cert is nil") } @@ -245,28 +251,23 @@ func (mgr *CredentialManager) getPivateKeyFromCert(userName string, cert []byte) if err != nil { return nil, errors.WithMessage(err, "fetching public key from cert failed") } - secProvider := mgr.config.SecurityProvider() - if secProvider == "SW" { - return mgr.getPivateKeyForSKIFromStore(userName, pubKey.SKI()) + privKey, err := mgr.getPrivateKeyFromKeyStore(userName, pubKey.SKI()) + if err == nil { + return privKey, nil } - return mgr.getPivateKeyForSKIFromHSM(pubKey.SKI()) + if err != kvstore.ErrNotFound { + return nil, errors.WithMessage(err, "fetching private key from key store failed") + } + return mgr.cryptoProvider.GetKey(pubKey.SKI()) } -func (mgr *CredentialManager) getPivateKeyForSKIFromStore(userName string, ski []byte) (apicryptosuite.Key, error) { - pemBytes, err := mgr.getStoredPrivateKeyPem(userName, ski) +func (mgr *CredentialManager) getPrivateKeyFromKeyStore(userName string, ski []byte) (apicryptosuite.Key, error) { + pemBytes, err := mgr.getPrivateKeyPemFromKeyStore(userName, ski) if err != nil { return nil, err } - if pemBytes == nil { - return nil, fmt.Errorf("private key not found in key store for user [%s]", userName) - } - privateKey, err := fabricCaUtil.ImportBCCSPKeyFromPEMBytes(pemBytes, mgr.cryptoProvider, true) - if err != nil { - return nil, errors.Wrapf(err, "import private key failed %v", pemBytes) + if pemBytes != nil { + return fabricCaUtil.ImportBCCSPKeyFromPEMBytes(pemBytes, mgr.cryptoProvider, true) } - return privateKey, nil -} - -func (mgr *CredentialManager) getPivateKeyForSKIFromHSM(ski []byte) (apicryptosuite.Key, error) { - return mgr.cryptoProvider.GetKey(ski) + return nil, kvstore.ErrNotFound } diff --git a/pkg/fabric-client/credentialmgr/credentialmgr_test.go b/pkg/fabric-client/credentialmgr/credentialmgr_test.go index 5129bd9ede..e064a71b89 100644 --- a/pkg/fabric-client/credentialmgr/credentialmgr_test.go +++ b/pkg/fabric-client/credentialmgr/credentialmgr_test.go @@ -38,16 +38,17 @@ func TestCredentialManager(t *testing.T) { t.Fatalf("Should have failed to retrieve signing identity for non-existent user") } - id, err := credentialMgr.GetSigningIdentity("User1") - if err != nil { - t.Fatalf("Failed to retrieve signing identity: %s", err) - } - if err := checkSigningIdentity(id); err != nil { + if err := checkSigningIdentity(credentialMgr, "User1"); err != nil { t.Fatalf("checkSigningIdentity failes: %s", err) } } -func checkSigningIdentity(id *apifabclient.SigningIdentity) error { +func checkSigningIdentity(credentialMgr apifabclient.CredentialManager, user string) error { + id, err := credentialMgr.GetSigningIdentity(user) + if err != nil { + return errors.Wrapf(err, "Failed to retrieve signing identity: %s", err) + } + if id == nil { return errors.New("SigningIdentity is nil") } @@ -100,35 +101,19 @@ func TestCredentialManagerFromEmbeddedCryptoConfig(t *testing.T) { t.Fatalf("Should have failed to retrieve signing identity for non-existent user") } - id, err := credentialMgr.GetSigningIdentity("EmbeddedUser") - if err != nil { - t.Fatalf("Failed to retrieve signing identity: %+v", err) - } - if err := checkSigningIdentity(id); err != nil { + if err := checkSigningIdentity(credentialMgr, "EmbeddedUser"); err != nil { t.Fatalf("checkSigningIdentity failes: %s", err) } - id, err = credentialMgr.GetSigningIdentity("EmbeddedUserWithPaths") - if err != nil { - t.Fatalf("Failed to retrieve signing identity: %+v", err) - } - if err := checkSigningIdentity(id); err != nil { + if err := checkSigningIdentity(credentialMgr, "EmbeddedUserWithPaths"); err != nil { t.Fatalf("checkSigningIdentity failes: %s", err) } - id, err = credentialMgr.GetSigningIdentity("EmbeddedUserMixed") - if err != nil { - t.Fatalf("Failed to retrieve signing identity: %+v", err) - } - if err := checkSigningIdentity(id); err != nil { + if err := checkSigningIdentity(credentialMgr, "EmbeddedUserMixed"); err != nil { t.Fatalf("checkSigningIdentity failes: %s", err) } - id, err = credentialMgr.GetSigningIdentity("EmbeddedUserMixed2") - if err != nil { - t.Fatalf("Failed to retrieve signing identity: %+v", err) - } - if err := checkSigningIdentity(id); err != nil { + if err := checkSigningIdentity(credentialMgr, "EmbeddedUserMixed2"); err != nil { t.Fatalf("checkSigningIdentity failes: %s", err) } } diff --git a/pkg/fabric-client/credentialmgr/enrollment_test.go b/pkg/fabric-client/credentialmgr/enrollment_test.go new file mode 100644 index 0000000000..f878291caf --- /dev/null +++ b/pkg/fabric-client/credentialmgr/enrollment_test.go @@ -0,0 +1,183 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package credentialmgr + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/golang/mock/gomock" + "github.com/hyperledger/fabric-sdk-go/api/apiconfig" + "github.com/hyperledger/fabric-sdk-go/api/apicryptosuite" + camocks "github.com/hyperledger/fabric-sdk-go/api/apifabca/mocks" + "github.com/hyperledger/fabric-sdk-go/api/kvstore" + "github.com/hyperledger/fabric-sdk-go/internal/github.com/hyperledger/fabric-ca/util" + "github.com/hyperledger/fabric-sdk-go/pkg/fabric-client/credentialmgr/persistence" + "github.com/pkg/errors" + + "github.com/hyperledger/fabric-sdk-go/pkg/config" + "github.com/hyperledger/fabric-sdk-go/pkg/cryptosuite/bccsp/sw" +) + +func TestCredentialManagerWithEnrollment(t *testing.T) { + config, err := config.FromFile("../../../test/fixtures/config/config_test.yaml")() + if err != nil { + t.Fatalf(err.Error()) + } + netConfig, err := config.NetworkConfig() + if err != nil { + t.Fatalf("NetworkConfig failed: %s", err) + } + orgName := "Org1" + orgConfig, ok := netConfig.Organizations[strings.ToLower(orgName)] + if !ok { + t.Fatalf("org config not found: %s", orgName) + } + + cs, err := sw.GetSuiteByConfig(config) + + credentialMgr, err := NewCredentialManager(orgName, config, cs) + if err != nil { + t.Fatalf("Failed to setup credential manager: %s", err) + } + + if err := checkSigningIdentity(credentialMgr, "User1"); err != nil { + t.Fatalf("checkSigningIdentity failed: %s", err) + } + + userToEnroll := "enrollmentID" + certLookupKey := &persistence.CertKey{ + MspID: orgConfig.MspID, + UserName: userToEnroll, + } + + // Refers to the same location used by the CredentialManager for looking up certs + certStore, err := getCertStore(config, orgName) + if err != nil { + t.Fatalf("NewFileCertStore failed: %v", err) + } + + // // Delete all private keys from the crypto suite store + keyStorePath := config.KeyStorePath() + err = cleanup(keyStorePath) + if err != nil { + t.Fatalf("cleanup keyStorePath failed: %v", err) + } + + // Delete userToEnroll from cert store in case it's there + err = certStore.Delete(certLookupKey) + if err != nil { + t.Fatalf("certStore.Delete failed: %v", err) + } + + if err := checkSigningIdentity(credentialMgr, userToEnroll); err == nil { + t.Fatalf("checkSigningIdentity should fail for user who hasn't been enrolled") + } + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + caClient := camocks.NewMockFabricCAClient(ctrl) + prepareForEnroll(t, caClient, cs) + + _, certBytes, err := caClient.Enroll(userToEnroll, "enrollmentSecret") + if err != nil { + t.Fatalf("fabricCAClient Enroll failed: %v", err) + } + if certBytes == nil || len(certBytes) == 0 { + t.Fatalf("Got an empty cert from Enrill()") + } + + err = certStore.Store(certLookupKey, certBytes) + if err != nil { + t.Fatalf("certStore.Store: %v", err) + } + + if err := checkSigningIdentity(credentialMgr, userToEnroll); err != nil { + t.Fatalf("checkSigningIdentity shouldn't fail for user who hasn been just enrolled: %s", err) + } +} + +// Simulate caClient.Enroll() +func prepareForEnroll(t *testing.T, mc *camocks.MockFabricCAClient, cs apicryptosuite.CryptoSuite) { + // A real caClient.Enroll() generates a CSR. In the process, a crypto suite generates + // a new key pair, and the private key is stored into crypto suite private key storage. + + // "Generated" private key + keyBytes := []byte(`-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg5Ahcehypz6IpAYy6 +DtIf5zZsRjP4PtsmDhLbBJsXmD6hRANCAAR+YRAn8dFpDQDyvDA7JKPl5PoZenj3 +m1KOnMry/mOZcnXnTIh2ASV4ss8VluzBcyHGAv7BCmxXxDkjcV9eybv8 +-----END PRIVATE KEY-----`) + + // "Generated" cert, the "result" of a CA CSR processing + certBytes := []byte(`-----BEGIN CERTIFICATE----- +MIICGjCCAcCgAwIBAgIRAIQkbh9nsGnLmDalAVlj8sUwCgYIKoZIzj0EAwIwczEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG +cmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMTE2Nh +Lm9yZzEuZXhhbXBsZS5jb20wHhcNMTcwNzI4MTQyNzIwWhcNMjcwNzI2MTQyNzIw +WjBbMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMN +U2FuIEZyYW5jaXNjbzEfMB0GA1UEAwwWQWRtaW5Ab3JnMS5leGFtcGxlLmNvbTBZ +MBMGByqGSM49AgEGCCqGSM49AwEHA0IABH5hECfx0WkNAPK8MDsko+Xk+hl6ePeb +Uo6cyvL+Y5lydedMiHYBJXiyzxWW7MFzIcYC/sEKbFfEOSNxX17Ju/yjTTBLMA4G +A1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMCsGA1UdIwQkMCKAIIeR0TY+iVFf +mvoEKwaToscEu43ZXSj5fTVJornjxDUtMAoGCCqGSM49BAMCA0gAMEUCIQDVf8cL +NrfToiPzJpEFPGF+/8CpzOkl91oz+XJsvdgf5wIgI/e8mpvpplUQbU52+LejA36D +CsbWERvZPjR/GFEDEvc= +-----END CERTIFICATE-----`) + + var privateKey apicryptosuite.Key + var err error + + mc.EXPECT().Enroll(gomock.Any(), gomock.Any()).Do(func(enrollmentID string, enrollmentSecret string) { + // Import the key into the crypto suite's private key storage. + // This is normally done by a crypto suite when a new key is generated + privateKey, err = util.ImportBCCSPKeyFromPEMBytes(keyBytes, cs, false) + }).Return(privateKey, certBytes, err) +} + +func getCertStore(config apiconfig.Config, orgName string) (kvstore.KVStore, error) { + netConfig, err := config.NetworkConfig() + if err != nil { + return nil, err + } + orgConfig, ok := netConfig.Organizations[strings.ToLower(orgName)] + if !ok { + return nil, errors.New("org config retrieval failed") + } + orgCryptoPathTemplate := orgConfig.CryptoPath + if !filepath.IsAbs(orgCryptoPathTemplate) { + orgCryptoPathTemplate = filepath.Join(config.CryptoConfigPath(), orgCryptoPathTemplate) + } + fmt.Printf("orgCryptoPathTemplate: %s\n", orgCryptoPathTemplate) + certStore, err := persistence.NewFileCertStore(orgCryptoPathTemplate) + if err != nil { + return nil, errors.Wrapf(err, "creating a cert store failed") + } + return certStore, nil +} + +func cleanup(storePath string) error { + d, err := os.Open(storePath) + if err != nil { + return err + } + defer d.Close() + names, err := d.Readdirnames(-1) + if err != nil { + return err + } + for _, name := range names { + err = os.RemoveAll(filepath.Join(storePath, name)) + if err != nil { + return err + } + } + return nil +} diff --git a/test/fixtures/config/config_test.yaml b/test/fixtures/config/config_test.yaml index 90a39a0660..be2d67dbeb 100755 --- a/test/fixtures/config/config_test.yaml +++ b/test/fixtures/config/config_test.yaml @@ -64,8 +64,7 @@ client: connection: 3s response: 5s - - # Needed to load users crypto keys and certs. + # Root of the MSP directories with keys and certs. cryptoconfig: path: ${GOPATH}/src/github.com/hyperledger/fabric-sdk-go/${CRYPTOCONFIG_FIXTURES_PATH} @@ -83,7 +82,7 @@ client: path: /tmp/msp # [Optional]. Specific to Composer environment. Not used by SDK Go. - wallet: wallet-name + #wallet: wallet-name # BCCSP config for the client. Used by GO SDK. BCCSP: @@ -176,7 +175,7 @@ organizations: Org1: mspid: Org1MSP - # Needed to load users crypto keys and certs for this org (absolute path or relative to global crypto path, DEV mode) + # This org's MSP store (absolute path or relative to client.cryptoconfig) cryptoPath: peerOrganizations/org1.example.com/users/{userName}@org1.example.com/msp peers: @@ -210,7 +209,7 @@ organizations: Org2: mspid: Org2MSP - # Needed to load users crypto keys and certs for this org (absolute path or relative to global crypto path, DEV mode) + # This org's MSP store (absolute path or relative to client.cryptoconfig) cryptoPath: peerOrganizations/org2.example.com/users/{userName}@org2.example.com/msp peers: