From 7d3bed02cbce08caa8733a952f86936c56732c3d Mon Sep 17 00:00:00 2001 From: "sudesh.shetty" Date: Thu, 22 Apr 2021 17:26:30 -0400 Subject: [PATCH] feat: vc wallet EDV storage - if wallet user provides EDV settings during profile creationfor storage then wallet will create internal EDV client instance for storing wallet contents - for now, encryption and MAC key IDs has to be provided by client user during profile creation/update - closes #2757 Signed-off-by: sudesh.shetty --- go.mod | 4 +- go.sum | 10 ++ pkg/wallet/contents.go | 23 ++-- pkg/wallet/contents_test.go | 205 +++++++++++++++-------------------- pkg/wallet/kmsclient_test.go | 37 ++++++- pkg/wallet/options.go | 17 ++- pkg/wallet/profile.go | 84 ++++++++++++-- pkg/wallet/profile_test.go | 89 +++++++++++++++ pkg/wallet/storage.go | 139 ++++++++++++++++++++++++ pkg/wallet/storage_test.go | 120 ++++++++++++++++++++ pkg/wallet/wallet.go | 18 ++- pkg/wallet/wallet_test.go | 96 +++++++++++++--- 12 files changed, 671 insertions(+), 171 deletions(-) create mode 100644 pkg/wallet/storage.go create mode 100644 pkg/wallet/storage_test.go diff --git a/go.mod b/go.mod index e659dbcb24..1f4aec5547 100644 --- a/go.mod +++ b/go.mod @@ -17,12 +17,12 @@ require ( github.com/google/tink/go v1.5.0 github.com/google/uuid v1.1.2 github.com/gorilla/mux v1.7.3 + github.com/hyperledger/aries-framework-go/component/storage/edv v0.0.0-20210422133815-2ef2d99cb692 github.com/hyperledger/aries-framework-go/component/storageutil v0.0.0-20210422133815-2ef2d99cb692 github.com/hyperledger/aries-framework-go/spi v0.0.0-20210422133815-2ef2d99cb692 github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a github.com/kawamuray/jsonpath v0.0.0-20201211160320-7483bafabd7e github.com/kilic/bls12-381 v0.0.0-20201104083100-a288617c07f1 - github.com/minio/sha256-simd v0.1.1 // indirect github.com/mitchellh/mapstructure v1.1.2 github.com/multiformats/go-multibase v0.0.1 github.com/multiformats/go-multihash v0.0.13 @@ -34,10 +34,8 @@ require ( github.com/teserakt-io/golang-ed25519 v0.0.0-20200315192543-8255be791ce4 github.com/tidwall/gjson v1.6.7 github.com/tidwall/sjson v1.1.4 - github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonschema v1.2.0 golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 - golang.org/x/sys v0.0.0-20201211090839-8ad439b19e0f // indirect google.golang.org/protobuf v1.25.0 nhooyr.io/websocket v1.8.3 ) diff --git a/go.sum b/go.sum index 0ed782b672..74e9f47e77 100644 --- a/go.sum +++ b/go.sum @@ -174,12 +174,22 @@ github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvh github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hyperledger/aries-framework-go v0.1.7-0.20210409151411-eeeb8508bd87/go.mod h1:9Mz/wkgyzXKxuOZObASCWRprt30P+xSsL08T8VBFRnk= +github.com/hyperledger/aries-framework-go/component/storage/edv v0.0.0-20210422133815-2ef2d99cb692 h1:anEytYaCtUeVS4UpivolNp8S4ZPLIQaBrJAiZMh5CwE= +github.com/hyperledger/aries-framework-go/component/storage/edv v0.0.0-20210422133815-2ef2d99cb692/go.mod h1:Vw8AblyCa1h6mVbNvbMXeZdlXVGu6Cq+TXZhD4oqvwE= +github.com/hyperledger/aries-framework-go/component/storageutil v0.0.0-20210320144851-40976de98ccf/go.mod h1:HVV8sifdHIyLkrlgmK/6+3YWKnOJPUfoNU+4SwQqMSs= +github.com/hyperledger/aries-framework-go/component/storageutil v0.0.0-20210409151411-eeeb8508bd87/go.mod h1:kJT7bcaKsvk1lMp2jqS8srF+ZUie2H4MoPbL2V29dgA= github.com/hyperledger/aries-framework-go/component/storageutil v0.0.0-20210422133815-2ef2d99cb692 h1:y9eMnxX9OyHsKTAn2d0HO2vcfvXwldtaexdYoRAagrk= github.com/hyperledger/aries-framework-go/component/storageutil v0.0.0-20210422133815-2ef2d99cb692/go.mod h1:uGc7F3tXQIY6xjs8VEI6/oxp4ZDXDfGjPMCTgax5Zhc= +github.com/hyperledger/aries-framework-go/spi v0.0.0-20210310001230-bc1bd8ea889c/go.mod h1:fDr9wW00GJJl1lR1SFHmJW8utIocdvjO5RNhAYS05EY= +github.com/hyperledger/aries-framework-go/spi v0.0.0-20210310160016-d5eea2ecdd50/go.mod h1:fDr9wW00GJJl1lR1SFHmJW8utIocdvjO5RNhAYS05EY= github.com/hyperledger/aries-framework-go/spi v0.0.0-20210320144851-40976de98ccf/go.mod h1:fDr9wW00GJJl1lR1SFHmJW8utIocdvjO5RNhAYS05EY= +github.com/hyperledger/aries-framework-go/spi v0.0.0-20210322152545-e6ebe2c79a2a/go.mod h1:fDr9wW00GJJl1lR1SFHmJW8utIocdvjO5RNhAYS05EY= github.com/hyperledger/aries-framework-go/spi v0.0.0-20210409151411-eeeb8508bd87/go.mod h1:dBYKKD8U8U9o0g5BdNFFaRtjt9KTkiAYfQt+TTp+w1o= github.com/hyperledger/aries-framework-go/spi v0.0.0-20210422133815-2ef2d99cb692 h1:CW4qks+VsTKaeIXwb8tbc68bjVU9n6o6ek8vB5wyh54= github.com/hyperledger/aries-framework-go/spi v0.0.0-20210422133815-2ef2d99cb692/go.mod h1:dBYKKD8U8U9o0g5BdNFFaRtjt9KTkiAYfQt+TTp+w1o= +github.com/hyperledger/aries-framework-go/test/component v0.0.0-20210310160016-d5eea2ecdd50/go.mod h1:AybsT4/saiuxdVhK5CgOLIkcNMPZtX3GAUMOjHcLLjk= +github.com/hyperledger/aries-framework-go/test/component v0.0.0-20210324232048-34ff560ed041/go.mod h1:eKGEEe+PJNDQo7kVif3sUKBWwnsQDkE3gD/QlpmukcQ= github.com/hyperledger/aries-framework-go/test/component v0.0.0-20210409151411-eeeb8508bd87 h1:eGEPJ7L77Ov7/dT7IJVGmyIbHwFGwGChBS1GixD88c8= github.com/hyperledger/aries-framework-go/test/component v0.0.0-20210409151411-eeeb8508bd87/go.mod h1:JHzDtgJLd0134iLFXLxGBjJF+Z+TgiElA/5oVgMazts= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= diff --git a/pkg/wallet/contents.go b/pkg/wallet/contents.go index 01865bc089..a714597959 100644 --- a/pkg/wallet/contents.go +++ b/pkg/wallet/contents.go @@ -100,29 +100,24 @@ var ( // contentStore is store for wallet contents for given user profile. type contentStore struct { - provider storage.Provider - storeID string + provider *storageProvider open storeOpenHandle close storeCloseHandle lock sync.RWMutex } // newContentStore returns new wallet content store instance. -func newContentStore(p storage.Provider, pr *profile) (*contentStore, error) { - err := p.SetStoreConfig(pr.ID, storage.StoreConfiguration{TagNames: []string{ - Collection.Name(), Credential.Name(), Connection.Name(), DIDResolutionResponse.Name(), Connection.Name(), Key.Name(), - }}) - if err != nil { - return nil, fmt.Errorf("failed to set store config for user '%s' : %w", pr.User, err) - } - - return &contentStore{open: storeLocked, close: noOp, provider: p, storeID: pr.ID}, nil +// will use underlying storage provider as content storage if profile doesn't have edv settings. +func newContentStore(p storage.Provider, pr *profile) *contentStore { + return &contentStore{open: storeLocked, close: noOp, provider: newWalletStorageProvider(pr, p)} } -func (cs *contentStore) Open() error { - store, err := cs.provider.OpenStore(cs.storeID) +func (cs *contentStore) Open(auth string) error { + store, err := cs.provider.OpenStore(auth, storage.StoreConfiguration{TagNames: []string{ + Collection.Name(), Credential.Name(), Connection.Name(), DIDResolutionResponse.Name(), Connection.Name(), Key.Name(), + }}) if err != nil { - return fmt.Errorf("failed to open store : %w", err) + return err } cs.lock.Lock() diff --git a/pkg/wallet/contents_test.go b/pkg/wallet/contents_test.go index fa5fa1a992..d2e75a4c07 100644 --- a/pkg/wallet/contents_test.go +++ b/pkg/wallet/contents_test.go @@ -188,45 +188,48 @@ func TestContentStores(t *testing.T) { t.Run("create new content store - success", func(t *testing.T) { sp := getMockStorageProvider() - contentStore, err := newContentStore(sp, &profile{ID: uuid.New().String()}) - require.NoError(t, err) + // create new store + contentStore := newContentStore(sp, &profile{ID: uuid.New().String()}) require.NotEmpty(t, contentStore) + require.Empty(t, sp.config.TagNames) + + // open store + require.NoError(t, contentStore.Open(token)) require.EqualValues(t, sp.config.TagNames, []string{"collection", "credential", "connection", "didResolutionResponse", "connection", "key"}) - }) - t.Run("create new content store - failure", func(t *testing.T) { - sp := getMockStorageProvider() - sp.failure = errors.New(sampleContenttErr) - - // set store config error - contentStore, err := newContentStore(sp, &profile{ID: uuid.New().String()}) - require.Empty(t, contentStore) - require.Error(t, err) - require.Contains(t, err.Error(), sampleContenttErr) - require.Contains(t, err.Error(), "failed to set store config for user") + // close store + contentStore.Close() + store, err := contentStore.open(token) + require.Empty(t, store) + require.True(t, errors.Is(err, ErrWalletLocked)) }) t.Run("open store - failure", func(t *testing.T) { sp := getMockStorageProvider() + contentStore := newContentStore(sp, &profile{ID: uuid.New().String()}) + // open store error - sp.failure = nil sp.ErrOpenStoreHandle = errors.New(sampleContenttErr) - - contentStore, err := newContentStore(sp, &profile{ID: uuid.New().String()}) - require.NoError(t, err) - - err = contentStore.Open() + err := contentStore.Open(token) require.Error(t, err) require.Contains(t, err.Error(), sampleContenttErr) require.Contains(t, err.Error(), "failed to open store") + // set store config error + sp.ErrOpenStoreHandle = nil + sp.failure = errors.New(sampleContenttErr) + err = contentStore.Open(token) + require.Error(t, err) + require.Contains(t, err.Error(), sampleContenttErr) + require.Contains(t, err.Error(), "failed to set store config") + sp.ErrOpenStoreHandle = nil + sp.failure = nil sp.Store.ErrClose = errors.New(sampleContenttErr) - contentStore, err = newContentStore(sp, &profile{ID: uuid.New().String()}) - require.NoError(t, err) - require.NoError(t, contentStore.Open()) + contentStore = newContentStore(sp, &profile{ID: uuid.New().String()}) + require.NoError(t, contentStore.Open(token)) contentStore.Close() }) @@ -234,13 +237,12 @@ func TestContentStores(t *testing.T) { t.Run("save to store - success", func(t *testing.T) { sp := getMockStorageProvider() - contentStore, err := newContentStore(sp, &profile{ID: uuid.New().String()}) - require.NoError(t, err) + contentStore := newContentStore(sp, &profile{ID: uuid.New().String()}) require.NotEmpty(t, contentStore) - require.NoError(t, contentStore.Open()) + require.NoError(t, contentStore.Open(token)) - err = contentStore.Save(token, Collection, []byte(sampleContentValid)) + err := contentStore.Save(token, Collection, []byte(sampleContentValid)) require.NoError(t, err) // store is open but invalid auth token @@ -255,26 +257,24 @@ func TestContentStores(t *testing.T) { t.Run("save content to store without ID - success", func(t *testing.T) { sp := getMockStorageProvider() - contentStore, err := newContentStore(sp, &profile{ID: uuid.New().String()}) - require.NoError(t, err) + contentStore := newContentStore(sp, &profile{ID: uuid.New().String()}) require.NotEmpty(t, contentStore) - require.NoError(t, contentStore.Open()) + require.NoError(t, contentStore.Open(token)) - err = contentStore.Save(token, Collection, []byte(sampleContentNoID)) + err := contentStore.Save(token, Collection, []byte(sampleContentNoID)) require.NoError(t, err) }) t.Run("save to doc resolution to store - success", func(t *testing.T) { sp := getMockStorageProvider() - contentStore, err := newContentStore(sp, &profile{ID: uuid.New().String()}) - require.NoError(t, err) + contentStore := newContentStore(sp, &profile{ID: uuid.New().String()}) require.NotEmpty(t, contentStore) - require.NoError(t, contentStore.Open()) + require.NoError(t, contentStore.Open(token)) - err = contentStore.Save(token, DIDResolutionResponse, []byte(didResolutionResult)) + err := contentStore.Save(token, DIDResolutionResponse, []byte(didResolutionResult)) require.NoError(t, err) // get by DID ID @@ -295,12 +295,11 @@ func TestContentStores(t *testing.T) { sp := getMockStorageProvider() sampleUser := uuid.New().String() - contentStore, err := newContentStore(sp, &profile{ID: sampleUser}) - require.NoError(t, err) + contentStore := newContentStore(sp, &profile{ID: sampleUser}) require.NotEmpty(t, contentStore) // wallet locked - err = contentStore.Save(sampleFakeTkn, Key, []byte(sampleKeyContentBase58Valid)) + err := contentStore.Save(sampleFakeTkn, Key, []byte(sampleKeyContentBase58Valid)) require.True(t, errors.Is(err, ErrWalletLocked)) err = contentStore.Save(sampleFakeTkn, Key, []byte(sampleKeyContentJwkValid)) @@ -341,12 +340,11 @@ func TestContentStores(t *testing.T) { sp := getMockStorageProvider() sampleUser := uuid.New().String() - contentStore, err := newContentStore(sp, &profile{ID: sampleUser}) - require.NoError(t, err) + contentStore := newContentStore(sp, &profile{ID: sampleUser}) require.NotEmpty(t, contentStore) // wallet locked - err = contentStore.Save(token, Key, []byte("")) + err := contentStore.Save(token, Key, []byte("")) require.Error(t, err) require.Contains(t, err.Error(), "failed to read key contents") }) @@ -354,11 +352,10 @@ func TestContentStores(t *testing.T) { t.Run("save to doc resolution to store - failure", func(t *testing.T) { sp := getMockStorageProvider() - contentStore, err := newContentStore(sp, &profile{ID: uuid.New().String()}) - require.NoError(t, err) + contentStore := newContentStore(sp, &profile{ID: uuid.New().String()}) require.NotEmpty(t, contentStore) - err = contentStore.Save(token, DIDResolutionResponse, []byte(sampleContentInvalid)) + err := contentStore.Save(token, DIDResolutionResponse, []byte(sampleContentInvalid)) require.Error(t, err) require.Contains(t, err.Error(), "invalid DID resolution response model") }) @@ -366,12 +363,11 @@ func TestContentStores(t *testing.T) { t.Run("save to store - failures", func(t *testing.T) { sp := getMockStorageProvider() - contentStore, err := newContentStore(sp, &profile{ID: uuid.New().String()}) - require.NoError(t, err) + contentStore := newContentStore(sp, &profile{ID: uuid.New().String()}) require.NotEmpty(t, contentStore) // invalid content type - err = contentStore.Save(token, ContentType("invalid"), []byte(sampleContentValid)) + err := contentStore.Save(token, ContentType("invalid"), []byte(sampleContentValid)) require.Error(t, err) require.Contains(t, err.Error(), "invalid content type 'invalid'") @@ -383,15 +379,14 @@ func TestContentStores(t *testing.T) { // store errors sp.Store.ErrPut = errors.New(sampleContenttErr) - contentStore, err = newContentStore(sp, &profile{ID: uuid.New().String()}) - require.NoError(t, err) + contentStore = newContentStore(sp, &profile{ID: uuid.New().String()}) require.NotEmpty(t, contentStore) // wallet locked err = contentStore.Save(token, Credential, []byte(sampleContentValid)) require.True(t, errors.Is(err, ErrWalletLocked)) - require.NoError(t, contentStore.Open()) + require.NoError(t, contentStore.Open(token)) err = contentStore.Save(token, Credential, []byte(sampleContentValid)) require.Error(t, err) @@ -406,11 +401,10 @@ func TestContentStores(t *testing.T) { t.Run("save to invalid content type - validation", func(t *testing.T) { sp := getMockStorageProvider() - contentStore, err := newContentStore(sp, &profile{ID: uuid.New().String()}) - require.NoError(t, err) + contentStore := newContentStore(sp, &profile{ID: uuid.New().String()}) require.NotEmpty(t, contentStore) - err = contentStore.Save(token, "Test", []byte("{}")) + err := contentStore.Save(token, "Test", []byte("{}")) require.Error(t, err) require.Contains(t, err.Error(), "invalid content type") }) @@ -418,13 +412,12 @@ func TestContentStores(t *testing.T) { t.Run("save duplicate items - validation", func(t *testing.T) { sp := getMockStorageProvider() - contentStore, err := newContentStore(sp, &profile{ID: uuid.New().String()}) - require.NoError(t, err) + contentStore := newContentStore(sp, &profile{ID: uuid.New().String()}) require.NotEmpty(t, contentStore) - require.NoError(t, contentStore.Open()) + require.NoError(t, contentStore.Open(token)) - err = contentStore.Save(token, Collection, []byte(sampleContentValid)) + err := contentStore.Save(token, Collection, []byte(sampleContentValid)) require.NoError(t, err) // save again @@ -436,14 +429,13 @@ func TestContentStores(t *testing.T) { t.Run("get from store - success", func(t *testing.T) { sp := getMockStorageProvider() - contentStore, err := newContentStore(sp, &profile{ID: uuid.New().String()}) - require.NoError(t, err) + contentStore := newContentStore(sp, &profile{ID: uuid.New().String()}) require.NotEmpty(t, contentStore) - require.NoError(t, contentStore.Open()) + require.NoError(t, contentStore.Open(token)) // save - err = contentStore.Save(token, Collection, []byte(sampleContentValid)) + err := contentStore.Save(token, Collection, []byte(sampleContentValid)) require.NoError(t, err) // get @@ -456,15 +448,14 @@ func TestContentStores(t *testing.T) { sp := getMockStorageProvider() sp.Store.ErrGet = errors.New(sampleContenttErr) - contentStore, err := newContentStore(sp, &profile{ID: uuid.New().String()}) - require.NoError(t, err) + contentStore := newContentStore(sp, &profile{ID: uuid.New().String()}) require.NotEmpty(t, contentStore) content, err := contentStore.Get(token, "did:example:123456789abcdefghi", Collection) require.Empty(t, content) require.True(t, errors.Is(err, ErrWalletLocked)) - require.NoError(t, contentStore.Open()) + require.NoError(t, contentStore.Open(token)) // get content, err = contentStore.Get(token, "did:example:123456789abcdefghi", Collection) @@ -476,14 +467,13 @@ func TestContentStores(t *testing.T) { t.Run("remove from store - success", func(t *testing.T) { sp := getMockStorageProvider() - contentStore, err := newContentStore(sp, &profile{ID: uuid.New().String()}) - require.NoError(t, err) + contentStore := newContentStore(sp, &profile{ID: uuid.New().String()}) require.NotEmpty(t, contentStore) - require.NoError(t, contentStore.Open()) + require.NoError(t, contentStore.Open(token)) // save - err = contentStore.Save(token, Collection, []byte(sampleContentValid)) + err := contentStore.Save(token, Collection, []byte(sampleContentValid)) require.NoError(t, err) // get @@ -506,14 +496,13 @@ func TestContentStores(t *testing.T) { sp := getMockStorageProvider() sp.Store.ErrDelete = errors.New(sampleContenttErr) - contentStore, err := newContentStore(sp, &profile{ID: uuid.New().String()}) - require.NoError(t, err) + contentStore := newContentStore(sp, &profile{ID: uuid.New().String()}) require.NotEmpty(t, contentStore) - require.NoError(t, contentStore.Open()) + require.NoError(t, contentStore.Open(token)) // save - err = contentStore.Save(token, Collection, []byte(sampleContentValid)) + err := contentStore.Save(token, Collection, []byte(sampleContentValid)) require.NoError(t, err) // remove @@ -566,11 +555,10 @@ func TestContentStore_GetAll(t *testing.T) { t.Run("get all content from store for credential type - success", func(t *testing.T) { sp := getMockStorageProvider() - contentStore, err := newContentStore(sp, &profile{ID: uuid.New().String()}) - require.NoError(t, err) + contentStore := newContentStore(sp, &profile{ID: uuid.New().String()}) require.NotEmpty(t, contentStore) - require.NoError(t, contentStore.Open()) + require.NoError(t, contentStore.Open(token)) // save test data const count = 5 @@ -604,14 +592,13 @@ func TestContentStore_GetAll(t *testing.T) { sp := getMockStorageProvider() // wallet locked - contentStore, err := newContentStore(sp, &profile{ID: uuid.New().String()}) - require.NoError(t, err) + contentStore := newContentStore(sp, &profile{ID: uuid.New().String()}) allVcs, err := contentStore.GetAll(token, Credential) require.True(t, errors.Is(err, ErrWalletLocked)) require.Empty(t, allVcs) - require.NoError(t, contentStore.Open()) + require.NoError(t, contentStore.Open(token)) require.NoError(t, contentStore.Save(token, Credential, []byte(fmt.Sprintf(vcContent, uuid.New().String())))) // iterator value error @@ -624,10 +611,9 @@ func TestContentStore_GetAll(t *testing.T) { // iterator value error sp.MockStoreProvider.Store.ErrKey = errors.New(sampleContenttErr + uuid.New().String()) - contentStore, err = newContentStore(sp, &profile{ID: uuid.New().String()}) - require.NoError(t, err) + contentStore = newContentStore(sp, &profile{ID: uuid.New().String()}) require.NotEmpty(t, contentStore) - require.NoError(t, contentStore.Open()) + require.NoError(t, contentStore.Open(token)) allVcs, err = contentStore.GetAll(token, Credential) require.True(t, errors.Is(err, sp.MockStoreProvider.Store.ErrKey)) @@ -636,10 +622,9 @@ func TestContentStore_GetAll(t *testing.T) { // iterator next error sp.MockStoreProvider.Store.ErrNext = errors.New(sampleContenttErr + uuid.New().String()) - contentStore, err = newContentStore(sp, &profile{ID: uuid.New().String()}) - require.NoError(t, err) + contentStore = newContentStore(sp, &profile{ID: uuid.New().String()}) require.NotEmpty(t, contentStore) - require.NoError(t, contentStore.Open()) + require.NoError(t, contentStore.Open(token)) require.NoError(t, contentStore.Save(token, Credential, []byte(fmt.Sprintf(vcContent, uuid.New().String())))) @@ -650,10 +635,9 @@ func TestContentStore_GetAll(t *testing.T) { // iterator next error sp.MockStoreProvider.Store.ErrQuery = errors.New(sampleContenttErr + uuid.New().String()) - contentStore, err = newContentStore(sp, &profile{ID: uuid.New().String()}) - require.NoError(t, err) + contentStore = newContentStore(sp, &profile{ID: uuid.New().String()}) require.NotEmpty(t, contentStore) - require.NoError(t, contentStore.Open()) + require.NoError(t, contentStore.Open(token)) allVcs, err = contentStore.GetAll(token, Credential) require.True(t, errors.Is(err, sp.MockStoreProvider.Store.ErrQuery)) @@ -669,13 +653,12 @@ func TestContentDIDResolver(t *testing.T) { t.Run("create new content store - success", func(t *testing.T) { sp := getMockStorageProvider() - contentStore, err := newContentStore(sp, &profile{ID: uuid.New().String()}) - require.NoError(t, err) + contentStore := newContentStore(sp, &profile{ID: uuid.New().String()}) require.NotEmpty(t, contentStore) - require.NoError(t, contentStore.Open()) + require.NoError(t, contentStore.Open(token)) // save custom DID - err = contentStore.Save(token, DIDResolutionResponse, []byte(sampleDocResolutionResponse)) + err := contentStore.Save(token, DIDResolutionResponse, []byte(sampleDocResolutionResponse)) require.NoError(t, err) contentVDR := newContentBasedVDR(token, &vdr.MockVDRegistry{}, contentStore) @@ -696,8 +679,7 @@ func TestContentDIDResolver(t *testing.T) { t.Run("create new content store - errors", func(t *testing.T) { sp := getMockStorageProvider() - contentStore, err := newContentStore(sp, &profile{ID: uuid.New().String()}) - require.NoError(t, err) + contentStore := newContentStore(sp, &profile{ID: uuid.New().String()}) require.NotEmpty(t, contentStore) contentVDR := newContentBasedVDR(token, &vdr.MockVDRegistry{}, contentStore) @@ -709,7 +691,7 @@ func TestContentDIDResolver(t *testing.T) { require.Empty(t, didDoc) // open store - require.NoError(t, contentStore.Open()) + require.NoError(t, contentStore.Open(token)) // DID not found didDoc, err = contentVDR.Resolve("did:key:invalid") @@ -794,10 +776,9 @@ func TestContentStore_Collections(t *testing.T) { t.Run("contents by collection - success", func(t *testing.T) { sp := getMockStorageProvider() - contentStore, err := newContentStore(sp, &profile{ID: uuid.New().String()}) - require.NoError(t, err) + contentStore := newContentStore(sp, &profile{ID: uuid.New().String()}) require.NotEmpty(t, contentStore) - require.NoError(t, contentStore.Open()) + require.NoError(t, contentStore.Open(token)) // save a collection require.NoError(t, contentStore.Save(token, Collection, []byte(orgCollection))) @@ -861,12 +842,11 @@ func TestContentStore_Collections(t *testing.T) { t.Run("contents by collection - failure", func(t *testing.T) { sp := getMockStorageProvider() - contentStore, err := newContentStore(sp, &profile{ID: uuid.New().String()}) - require.NoError(t, err) + contentStore := newContentStore(sp, &profile{ID: uuid.New().String()}) require.NotEmpty(t, contentStore) - require.NoError(t, contentStore.Open()) + require.NoError(t, contentStore.Open(token)) - err = contentStore.Save(token, + err := contentStore.Save(token, DIDResolutionResponse, []byte(didResolutionResult), AddByCollection(collectionID+"invalid")) require.Error(t, err) require.Contains(t, err.Error(), "failed to find existing collection") @@ -884,10 +864,9 @@ func TestContentStore_Collections(t *testing.T) { // get content error sp.MockStoreProvider.Store.ErrGet = errors.New(sampleContenttErr + uuid.New().String()) - contentStore, err = newContentStore(sp, &profile{ID: uuid.New().String()}) - require.NoError(t, err) + contentStore = newContentStore(sp, &profile{ID: uuid.New().String()}) require.NotEmpty(t, contentStore) - require.NoError(t, contentStore.Open()) + require.NoError(t, contentStore.Open(token)) allVcs, err := contentStore.GetAllByCollection(token, collectionID, Credential) require.True(t, errors.Is(err, sp.MockStoreProvider.Store.ErrGet)) @@ -896,10 +875,9 @@ func TestContentStore_Collections(t *testing.T) { // iterator value error sp.MockStoreProvider.Store.ErrValue = errors.New(sampleContenttErr + uuid.New().String()) - contentStore, err = newContentStore(sp, &profile{ID: uuid.New().String()}) - require.NoError(t, err) + contentStore = newContentStore(sp, &profile{ID: uuid.New().String()}) require.NotEmpty(t, contentStore) - require.NoError(t, contentStore.Open()) + require.NoError(t, contentStore.Open(token)) allVcs, err = contentStore.GetAllByCollection(token, collectionID, Credential) require.True(t, errors.Is(err, sp.MockStoreProvider.Store.ErrValue)) @@ -908,10 +886,9 @@ func TestContentStore_Collections(t *testing.T) { // iterator value error sp.MockStoreProvider.Store.ErrKey = errors.New(sampleContenttErr + uuid.New().String()) - contentStore, err = newContentStore(sp, &profile{ID: uuid.New().String()}) - require.NoError(t, err) + contentStore = newContentStore(sp, &profile{ID: uuid.New().String()}) require.NotEmpty(t, contentStore) - require.NoError(t, contentStore.Open()) + require.NoError(t, contentStore.Open(token)) allVcs, err = contentStore.GetAllByCollection(token, collectionID, Credential) require.True(t, errors.Is(err, sp.MockStoreProvider.Store.ErrKey)) @@ -920,10 +897,9 @@ func TestContentStore_Collections(t *testing.T) { // iterator next error sp.MockStoreProvider.Store.ErrNext = errors.New(sampleContenttErr + uuid.New().String()) - contentStore, err = newContentStore(sp, &profile{ID: uuid.New().String()}) - require.NoError(t, err) + contentStore = newContentStore(sp, &profile{ID: uuid.New().String()}) require.NotEmpty(t, contentStore) - require.NoError(t, contentStore.Open()) + require.NoError(t, contentStore.Open(token)) allVcs, err = contentStore.GetAllByCollection(token, collectionID, Credential) require.True(t, errors.Is(err, sp.MockStoreProvider.Store.ErrNext)) @@ -932,10 +908,9 @@ func TestContentStore_Collections(t *testing.T) { // iterator next error sp.MockStoreProvider.Store.ErrQuery = errors.New(sampleContenttErr + uuid.New().String()) - contentStore, err = newContentStore(sp, &profile{ID: uuid.New().String()}) - require.NoError(t, err) + contentStore = newContentStore(sp, &profile{ID: uuid.New().String()}) require.NotEmpty(t, contentStore) - require.NoError(t, contentStore.Open()) + require.NoError(t, contentStore.Open(token)) allVcs, err = contentStore.GetAllByCollection(token, collectionID, Credential) require.True(t, errors.Is(err, sp.MockStoreProvider.Store.ErrQuery)) diff --git a/pkg/wallet/kmsclient_test.go b/pkg/wallet/kmsclient_test.go index aa2313932c..f987612a19 100644 --- a/pkg/wallet/kmsclient_test.go +++ b/pkg/wallet/kmsclient_test.go @@ -10,12 +10,14 @@ import ( "crypto/sha256" "errors" "testing" + "time" "github.com/google/uuid" "github.com/stretchr/testify/require" kmsapi "github.com/hyperledger/aries-framework-go/pkg/kms" - "github.com/hyperledger/aries-framework-go/pkg/mock/kms" + mockcrypto "github.com/hyperledger/aries-framework-go/pkg/mock/crypto" + mockkms "github.com/hyperledger/aries-framework-go/pkg/mock/kms" mockstorage "github.com/hyperledger/aries-framework-go/pkg/mock/storage" "github.com/hyperledger/aries-framework-go/pkg/secretlock/local/masterlock/pbkdf2" ) @@ -556,7 +558,7 @@ func TestImportKeyBase58(t *testing.T) { sampleErr := errors.New(sampleKeyMgrErr) wkmgr := keyManager() err := wkmgr.saveKeyManger(uuid.New().String(), mockToken, - &kms.KeyManager{ImportPrivateKeyErr: sampleErr}, 0) + &mockkms.KeyManager{ImportPrivateKeyErr: sampleErr}, 0) require.NoError(t, err) err = importKeyBase58(mockToken, &keyContent{ @@ -576,3 +578,34 @@ func TestImportKeyBase58(t *testing.T) { require.True(t, errors.Is(err, sampleErr)) }) } + +func TestKMSSigner(t *testing.T) { + token := uuid.New().String() + + require.NoError(t, keyManager().saveKeyManger(uuid.New().String(), token, &mockkms.KeyManager{}, 500*time.Millisecond)) + + t.Run("test kms signer errors", func(t *testing.T) { + // invalid auth + signer, err := newKMSSigner("invalid", &mockcrypto.Crypto{}, &ProofOptions{}) + require.True(t, errors.Is(err, ErrWalletLocked)) + require.Empty(t, signer) + + // invalid verification method + signer, err = newKMSSigner(token, &mockcrypto.Crypto{}, &ProofOptions{ + VerificationMethod: "invalid", + }) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid verification method format") + require.Empty(t, signer) + + // sign error + signer, err = newKMSSigner(token, &mockcrypto.Crypto{SignErr: errors.New(sampleKeyMgrErr)}, &ProofOptions{ + VerificationMethod: "did:example#123", + }) + require.NoError(t, err) + + res, err := signer.Sign([]byte("1234")) + require.Error(t, err) + require.Empty(t, res) + }) +} diff --git a/pkg/wallet/options.go b/pkg/wallet/options.go index 9e616b2b23..f5c9de9daa 100644 --- a/pkg/wallet/options.go +++ b/pkg/wallet/options.go @@ -24,8 +24,7 @@ type profileOpts struct { keyServerURL string // EDV options - edvServerURL string - vaultID string + edvConf *edvConf } // ProfileOptions is option for verifiable credential wallet key manager. @@ -53,6 +52,20 @@ func WithKeyServerURL(url string) ProfileOptions { } } +// WithEDVStorage option, for wallet profile to use EDV as storage. +// If provided then all wallet contents will use EDV for storage. +// Note: key manager options supplied for profile creation and management will be reused for EDV operations. +func WithEDVStorage(url, vaultID, encryptionKID, macKID string) ProfileOptions { + return func(opts *profileOpts) { + opts.edvConf = &edvConf{ + ServerURL: url, + VaultID: vaultID, + EncryptionKeyID: encryptionKID, + MACKeyID: macKID, + } + } +} + // unlockOpts contains options for unlocking VC wallet client. type unlockOpts struct { // local kms options diff --git a/pkg/wallet/profile.go b/pkg/wallet/profile.go index 287550e086..2fa3d0cd4e 100644 --- a/pkg/wallet/profile.go +++ b/pkg/wallet/profile.go @@ -13,6 +13,7 @@ import ( "github.com/google/uuid" + "github.com/hyperledger/aries-framework-go/pkg/kms" "github.com/hyperledger/aries-framework-go/pkg/secretlock" "github.com/hyperledger/aries-framework-go/spi/storage" ) @@ -39,11 +40,22 @@ type profile struct { // KeyServerURL for remotekms. KeyServerURL string - // EDVServerURL for encrypted data vault storage of wallet contents. - EDVServerURL string + // EDV configuration + EDVConf *edvConf +} + +type edvConf struct { + // ServerURL for encrypted data vault storage of wallet contents. + ServerURL string // VaultID for encrypted data vault storage of wallet contents. VaultID string + + // Key ID for encryption key for EDV. + EncryptionKeyID string + + // Key ID for MAC key for EDV. + MACKeyID string } // createProfile creates new verifiable credential wallet profile for given user and saves it in store. @@ -56,7 +68,10 @@ func createProfile(user string, opts *profileOpts) (*profile, error) { return nil, err } - profile.setEDVOptions(opts.edvServerURL, opts.vaultID) + err = profile.setEDVOptions(opts.edvConf) + if err != nil { + return nil, err + } return profile, nil } @@ -94,9 +109,64 @@ func (pr *profile) setKMSOptions(passphrase string, secretLockSvc secretlock.Ser return nil } -func (pr *profile) setEDVOptions(edvServerURL, vaultID string) { - pr.EDVServerURL = edvServerURL - pr.VaultID = vaultID +func (pr *profile) setEDVOptions(opts *edvConf) error { + // non EDV users. + if opts == nil { + return nil + } + + if opts.ServerURL == "" || opts.VaultID == "" || opts.EncryptionKeyID == "" || opts.MACKeyID == "" { + return errors.New("invalid EDV settings in profile") + } + + pr.EDVConf = opts + + return nil +} + +// nolint:gocyclo +func (pr *profile) setupEDVKeys(auth string, encryptionKeyType, macKeyType kms.KeyType) (bool, error) { + setupEncKey := pr.EDVConf != nil && pr.EDVConf.EncryptionKeyID == "" + setupMacKey := pr.EDVConf != nil && pr.EDVConf.MACKeyID == "" + + if !(setupEncKey || setupMacKey) { + return false, nil + } + + keyMgr, err := keyManager().getKeyManger(auth) + if err != nil { + return false, err + } + + // if encryption key is not yet setup yet then create one and set. + if setupEncKey { + if encryptionKeyType == "" { + encryptionKeyType = kms.NISTP256ECDHKWType + } + + kid, _, err := keyMgr.Create(encryptionKeyType) + if err != nil { + return false, err + } + + pr.EDVConf.EncryptionKeyID = kid + } + + // if MAC key is not yet setup yet then create one and set. + if setupMacKey { + if macKeyType == "" { + macKeyType = kms.HMACSHA256Tag256Type + } + + kid, _, err := keyMgr.Create(macKeyType) + if err != nil { + return false, err + } + + pr.EDVConf.MACKeyID = kid + } + + return true, nil } func (pr *profile) resetKMSOptions() { @@ -147,7 +217,7 @@ func (p *profileStore) get(user string) (*profile, error) { } // save saves profile into store, -// if argument 'override=true' then replaces existing profile for given user else returns error. +// if argument 'override=true' then replaces existing profile else returns error. func (p *profileStore) save(val *profile, override bool) error { if !override { profileBytes, _ := p.get(val.User) //nolint: errcheck diff --git a/pkg/wallet/profile_test.go b/pkg/wallet/profile_test.go index f0ba37b520..81073ab53c 100644 --- a/pkg/wallet/profile_test.go +++ b/pkg/wallet/profile_test.go @@ -7,11 +7,16 @@ SPDX-License-Identifier: Apache-2.0 package wallet import ( + "errors" "fmt" "testing" + "time" + "github.com/google/uuid" "github.com/stretchr/testify/require" + kmsapi "github.com/hyperledger/aries-framework-go/pkg/kms" + mockkms "github.com/hyperledger/aries-framework-go/pkg/mock/kms" "github.com/hyperledger/aries-framework-go/pkg/mock/secretlock" mockstorage "github.com/hyperledger/aries-framework-go/pkg/mock/storage" ) @@ -59,6 +64,30 @@ func TestCreateNewProfile(t *testing.T) { require.Equal(t, profile.MasterLockCipher, sampleMasterCipherText) }) + t.Run("test create new profile with EDV conf", func(t *testing.T) { + profile, err := createProfile(sampleProfileUser, + &profileOpts{ + passphrase: samplePassPhrase, secretLockSvc: nil, keyServerURL: sampleKeyServerURL, + edvConf: &edvConf{ + ServerURL: "sample-server-url", + VaultID: "sample-vault-ID", + EncryptionKeyID: "sample-enc-kid", + MACKeyID: "sample-mac-kid", + }, + }) + + require.NoError(t, err) + require.NotEmpty(t, profile) + require.NotEmpty(t, profile.ID) + require.Empty(t, profile.KeyServerURL, "") + require.NotEmpty(t, profile.MasterLockCipher) + require.NotEmpty(t, profile.EDVConf) + require.NotEmpty(t, profile.EDVConf.ServerURL) + require.NotEmpty(t, profile.EDVConf.VaultID) + require.NotEmpty(t, profile.EDVConf.EncryptionKeyID) + require.NotEmpty(t, profile.EDVConf.MACKeyID) + }) + t.Run("test create new profile failure", func(t *testing.T) { // invalid profile option profile, err := createProfile(sampleProfileUser, @@ -78,6 +107,66 @@ func TestCreateNewProfile(t *testing.T) { require.Error(t, err) require.Contains(t, err.Error(), "failed to create master lock from secret lock service provided") require.Contains(t, err.Error(), sampleCustomProfileErr) + + // invalid EDV settings + profile, err = createProfile(sampleProfileUser, + &profileOpts{ + passphrase: samplePassPhrase, secretLockSvc: nil, keyServerURL: sampleKeyServerURL, + edvConf: &edvConf{ + ServerURL: "sample-server-url", + }, + }) + require.Empty(t, profile) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid EDV settings in profile") + }) + + t.Run("test setup edv keys", func(t *testing.T) { + sampleProfile := &profile{ + EDVConf: &edvConf{ + ServerURL: "sample-server-url", + VaultID: "sample-vault-id", + }, + } + + token := uuid.New().String() + kid := "sample-kid" + kms := &mockkms.KeyManager{CreateKeyID: kid} + + require.NoError(t, keyManager().saveKeyManger(uuid.New().String(), token, kms, 500*time.Millisecond)) + + // setup edv keys + ok, err := sampleProfile.setupEDVKeys(token, kmsapi.NISTP256ECDHKWType, kmsapi.HMACSHA256Tag256Type) + require.NoError(t, err) + require.True(t, ok) + require.Equal(t, sampleProfile.EDVConf.EncryptionKeyID, kid) + require.Equal(t, sampleProfile.EDVConf.MACKeyID, kid) + + // no update + ok, err = sampleProfile.setupEDVKeys(token, "", "") + require.NoError(t, err) + require.False(t, ok) + + // test create key error + require.NoError(t, keyManager().saveKeyManger(uuid.New().String(), token, + &mockkms.KeyManager{CreateKeyErr: errors.New(sampleKeyMgrErr)}, 500*time.Millisecond)) + + sampleProfile.EDVConf.MACKeyID = "" + ok, err = sampleProfile.setupEDVKeys(token, "", "") + require.Error(t, err) + require.Contains(t, err.Error(), sampleKeyMgrErr) + require.False(t, ok) + + sampleProfile.EDVConf.EncryptionKeyID = "" + ok, err = sampleProfile.setupEDVKeys(token, "", "") + require.Error(t, err) + require.Contains(t, err.Error(), sampleKeyMgrErr) + require.False(t, ok) + + // invalid auth + ok, err = sampleProfile.setupEDVKeys(token+"invalid", "", "") + require.Error(t, err) + require.False(t, ok) }) } diff --git a/pkg/wallet/storage.go b/pkg/wallet/storage.go new file mode 100644 index 0000000000..1761f0770c --- /dev/null +++ b/pkg/wallet/storage.go @@ -0,0 +1,139 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +//nolint +package wallet + +import ( + "encoding/json" + "errors" + "fmt" + + "github.com/bluele/gcache" + + "github.com/hyperledger/aries-framework-go/component/storage/edv" + "github.com/hyperledger/aries-framework-go/pkg/crypto" + "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto" + "github.com/hyperledger/aries-framework-go/pkg/didcomm/packer" + "github.com/hyperledger/aries-framework-go/pkg/doc/jose" + "github.com/hyperledger/aries-framework-go/pkg/kms" + "github.com/hyperledger/aries-framework-go/spi/storage" +) + +// storageProvider all wallet content store storage provider operations. +// if profile has EDV settings then call to OpenStore will return EDV store instance by creating new EDV provider. +// Otherwise default provider will be used to open store. +// (Refer #2745 for more details) +type storageProvider struct { + profile *profile + defaultProvider storage.Provider +} + +func newWalletStorageProvider(profile *profile, provider storage.Provider) *storageProvider { + return &storageProvider{profile: profile, defaultProvider: provider} +} + +// OpenStore opens and returns store and sets store config to provider. +// if wallet profile has EDV settings then auth provided will be used to initialize edv storage provider. +func (s *storageProvider) OpenStore(auth string, config storage.StoreConfiguration) (storage.Store, error) { + var provider storage.Provider + var err error + + if s.profile.EDVConf != nil { + provider, err = createEDVStorageProvider(auth, s.profile.EDVConf) + if err != nil { + return nil, err + } + } else { + provider = s.defaultProvider + } + + store, err := provider.OpenStore(s.profile.ID) + if err != nil { + return nil, fmt.Errorf("failed to open store : %w", err) + } + + err = provider.SetStoreConfig(s.profile.ID, config) + if err != nil { + e := store.Close() + if e != nil { + logger.Warnf("failed to close store: %s", e) + } + + return nil, fmt.Errorf("failed to set store config: %w", err) + } + + return store, nil +} + +func createEDVStorageProvider(auth string, conf *edvConf) (storage.Provider, error) { + // get key manager + keyMgr, err := keyManager().getKeyManger(auth) + if err != nil { + if errors.Is(err, gcache.KeyNotFoundError) { + return nil, ErrWalletLocked + } + + return nil, err + } + + // get crypto + cryptoImpl, err := tinkcrypto.New() + if err != nil { + return nil, fmt.Errorf("failed to create crypto: %w", err) + } + + // get jwe encrypter + jweEncrypter, err := getJWSEncrypter(conf.EncryptionKeyID, keyMgr, cryptoImpl) + if err != nil { + return nil, fmt.Errorf("failed to create JWE encrypter: %w", err) + } + + // get jwe decrypter + jweDecrypter := jose.NewJWEDecrypt(nil, cryptoImpl, keyMgr) + + // get MAC crypto + macCrypto, err := getMacCrypto(conf.MACKeyID, keyMgr, cryptoImpl) + if err != nil { + return nil, fmt.Errorf("failed to create mac crypto: %w", err) + } + + // create EDV provider + // TODO add zcap header support + return edv.NewRESTProvider(conf.ServerURL, conf.VaultID, + edv.NewEncryptedFormatter(jweEncrypter, jweDecrypter, macCrypto, edv.WithDeterministicDocumentIDs()), + edv.WithFullDocumentsReturnedFromQueries(), edv.WithBatchEndpointExtension()), nil +} + +// getJWSEncrypter creates and returns jwe encrypter based on key manager & crypto provided +func getJWSEncrypter(kid string, keyMgr kms.KeyManager, cryptoImpl crypto.Crypto) (*jose.JWEEncrypt, error) { + pubKeyBytes, err := keyMgr.ExportPubKeyBytes(kid) + if err != nil { + return nil, err + } + + ecPubKey := new(crypto.PublicKey) + + ecPubKey.KID = kid + + err = json.Unmarshal(pubKeyBytes, ecPubKey) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal JWE public key bytes to an EC public key: %w", err) + } + + return jose.NewJWEEncrypt(jose.A256GCM, packer.EnvelopeEncodingTypeV2, "", "", nil, + []*crypto.PublicKey{ecPubKey}, cryptoImpl) +} + +// getMacCrypto creates and returns MAC crypto based on key manager & crypto provided +func getMacCrypto(kid string, keyMgr kms.KeyManager, cryptoImpl crypto.Crypto) (*edv.MACCrypto, error) { + keyHandle, err := keyMgr.Get(kid) + if err != nil { + return nil, err + } + + return edv.NewMACCrypto(keyHandle, cryptoImpl), nil +} diff --git a/pkg/wallet/storage_test.go b/pkg/wallet/storage_test.go new file mode 100644 index 0000000000..d3f4d5dcac --- /dev/null +++ b/pkg/wallet/storage_test.go @@ -0,0 +1,120 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package wallet + +import ( + "errors" + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/require" + + mockcrypto "github.com/hyperledger/aries-framework-go/pkg/mock/crypto" + mockkms "github.com/hyperledger/aries-framework-go/pkg/mock/kms" + "github.com/hyperledger/aries-framework-go/spi/storage" +) + +func TestStorageProvider_OpenStore(t *testing.T) { + sampleUser := uuid.New().String() + masterLock, err := getDefaultSecretLock(samplePassPhrase) + require.NoError(t, err) + + masterLockCipherText, err := createMasterLock(masterLock) + require.NoError(t, err) + require.NotEmpty(t, masterLockCipherText) + + profileInfo := &profile{ + User: sampleUser, + MasterLockCipher: masterLockCipherText, + } + + token, err := keyManager().createKeyManager(profileInfo, getMockStorageProvider(), + &unlockOpts{passphrase: samplePassPhrase}) + require.NoError(t, err) + require.NotEmpty(t, token) + + t.Run("successfully open store", func(t *testing.T) { + sp := getMockStorageProvider() + sampleProfile := &profile{ID: uuid.New().String(), User: uuid.New().String()} + wsp := newWalletStorageProvider(sampleProfile, sp) + + store, err := wsp.OpenStore(token, storage.StoreConfiguration{TagNames: []string{Credential.Name()}}) + require.NoError(t, err) + require.NotEmpty(t, store) + require.Len(t, sp.config.TagNames, 1) + }) + + t.Run("successfully open EDV store", func(t *testing.T) { + sampleProfile := &profile{ID: uuid.New().String(), User: uuid.New().String(), EDVConf: &edvConf{ + ServerURL: "sample-server", + VaultID: "sample-vault-ID", + }} + + ok, err := sampleProfile.setupEDVKeys(token, "", "") + require.NoError(t, err) + require.True(t, ok) + + wsp := newWalletStorageProvider(sampleProfile, nil) + + store, err := wsp.OpenStore(token, storage.StoreConfiguration{TagNames: []string{Credential.Name()}}) + require.NoError(t, err) + require.NotEmpty(t, store) + }) + + t.Run("failed to open EDV store", func(t *testing.T) { + sampleProfile := &profile{ID: uuid.New().String(), User: uuid.New().String(), EDVConf: &edvConf{ + ServerURL: "sample-server", + VaultID: "sample-vault-ID", + }} + + ok, err := sampleProfile.setupEDVKeys(token, "", "") + require.NoError(t, err) + require.True(t, ok) + + wsp := newWalletStorageProvider(sampleProfile, nil) + + // invalid auth + store, err := wsp.OpenStore(token+".", storage.StoreConfiguration{TagNames: []string{Credential.Name()}}) + require.Error(t, err) + require.True(t, errors.Is(err, ErrWalletLocked)) + require.Empty(t, store) + + // incorrect mac key ID + wsp.profile.EDVConf.MACKeyID += "x" + store, err = wsp.OpenStore(token, storage.StoreConfiguration{TagNames: []string{Credential.Name()}}) + require.Error(t, err) + require.Contains(t, err.Error(), "failed to create mac crypto") + require.Empty(t, store) + + // incorrect encryption key ID + wsp.profile.EDVConf.EncryptionKeyID += "x" + store, err = wsp.OpenStore(token, storage.StoreConfiguration{TagNames: []string{Credential.Name()}}) + require.Error(t, err) + require.Contains(t, err.Error(), "failed to create JWE encrypter") + require.Empty(t, store) + + val, err := getJWSEncrypter("sample-kid", &mockkms.KeyManager{ + ExportPubKeyBytesValue: []byte("invalid"), + }, &mockcrypto.Crypto{}) + require.Error(t, err) + require.Contains(t, err.Error(), "failed to unmarshal JWE public key bytes to an EC public key") + require.Empty(t, val) + }) + + t.Run("failed to set store config", func(t *testing.T) { + sp := getMockStorageProvider() + sampleProfile := &profile{ID: uuid.New().String(), User: uuid.New().String()} + wsp := newWalletStorageProvider(sampleProfile, sp) + + sp.failure = errors.New(sampleWalletErr) + sp.Store.ErrClose = errors.New(sampleWalletErr) + + store, err := wsp.OpenStore(token, storage.StoreConfiguration{TagNames: []string{Credential.Name()}}) + require.Error(t, err) + require.Empty(t, store) + }) +} diff --git a/pkg/wallet/wallet.go b/pkg/wallet/wallet.go index a146512dfe..c689afb683 100644 --- a/pkg/wallet/wallet.go +++ b/pkg/wallet/wallet.go @@ -106,17 +106,12 @@ func New(userID string, ctx provider) (*Wallet, error) { return nil, fmt.Errorf("failed to get VC wallet profile: %w", err) } - contents, err := newContentStore(ctx.StorageProvider(), profile) - if err != nil { - return nil, fmt.Errorf("failed to get wallet content store: %w", err) - } - return &Wallet{ userID: userID, profile: profile, storeProvider: ctx.StorageProvider(), walletCrypto: ctx.Crypto(), - contents: contents, + contents: newContentStore(ctx.StorageProvider(), profile), vdr: ctx.VDRegistry(), jsonldDocumentLoader: ctx.JSONLDDocumentLoader(), }, nil @@ -152,6 +147,7 @@ func createOrUpdate(userID string, ctx provider, update bool, options ...Profile var profile *profile + // nolint: nestif if update { // find existing profile and update it. profile, err = store.get(userID) @@ -164,7 +160,10 @@ func createOrUpdate(userID string, ctx provider, update bool, options ...Profile return fmt.Errorf("failed to update wallet user profile KMS options: %w", err) } - profile.setEDVOptions(opts.edvServerURL, opts.vaultID) + err = profile.setEDVOptions(opts.edvConf) + if err != nil { + return fmt.Errorf("failed to update EDV configuration") + } } else { // create new profile. profile, err = createProfile(userID, opts) @@ -201,9 +200,8 @@ func (c *Wallet) Open(options ...UnlockOptions) (string, error) { return "", err } - // unlock content store - // TODO token to be passed to contents.Open() for EDV support - err = c.contents.Open() + // open content store using token + err = c.contents.Open(token) if err != nil { // close wallet if it fails to open store c.Close() diff --git a/pkg/wallet/wallet_test.go b/pkg/wallet/wallet_test.go index f661d1d627..955f6c412c 100644 --- a/pkg/wallet/wallet_test.go +++ b/pkg/wallet/wallet_test.go @@ -168,6 +168,10 @@ const ( didKeyBBS = "did:key:zUC72c7u4BYVmfYinDceXkNAwzPEyuEE23kUmJDjLy8495KH3pjLwFhae1Fww9qxxRdLnS2VNNwni6W3KbYZKsicDtiNNEp76fYWR6HCD8jAz6ihwmLRjcHH6kB294Xfg1SL1qQ" pkBBSBase58 = "6gsgGpdx7p1nYoKJ4b5fKt1xEomWdnemg9nJFX6mqNCh" keyIDBBS = "zUC72c7u4BYVmfYinDceXkNAwzPEyuEE23kUmJDjLy8495KH3pjLwFhae1Fww9qxxRdLnS2VNNwni6W3KbYZKsicDtiNNEp76fYWR6HCD8jAz6ihwmLRjcHH6kB294Xfg1SL1qQ" + sampleEDVServerURL = "sample-edv-url" + sampleEDVVaultID = "sample-edv-vault-id" + sampleEDVEncryptionKID = "sample-edv-encryption-kid" + sampleEDVMacKID = "sample-edv-mac-kid" ) func TestCreate(t *testing.T) { @@ -201,6 +205,22 @@ func TestCreate(t *testing.T) { require.NotEmpty(t, wallet) }) + t.Run("test create new wallet using remote kms key server URL & EDV", func(t *testing.T) { + mockctx := newMockProvider(t) + err := CreateProfile(sampleUserID, mockctx, WithKeyServerURL(sampleKeyServerURL), + WithEDVStorage(sampleEDVServerURL, sampleEDVVaultID, sampleEDVEncryptionKID, sampleEDVMacKID)) + require.NoError(t, err) + + wallet, err := New(sampleUserID, mockctx) + require.NoError(t, err) + require.NotEmpty(t, wallet) + require.NotEmpty(t, wallet.profile.EDVConf) + require.Equal(t, wallet.profile.EDVConf.ServerURL, sampleEDVServerURL) + require.Equal(t, wallet.profile.EDVConf.VaultID, sampleEDVVaultID) + require.Equal(t, wallet.profile.EDVConf.EncryptionKeyID, sampleEDVEncryptionKID) + require.Equal(t, wallet.profile.EDVConf.MACKeyID, sampleEDVMacKID) + }) + t.Run("test create new wallet failure", func(t *testing.T) { mockctx := newMockProvider(t) err := CreateProfile(sampleUserID, mockctx) @@ -243,22 +263,6 @@ func TestCreate(t *testing.T) { require.Error(t, err) require.Empty(t, wallet) }) - - t.Run("test create new wallet failure - create content store error", func(t *testing.T) { - mockctx := newMockProvider(t) - mockctx.StorageProviderValue = &mockStorageProvider{ - MockStoreProvider: mockstorage.NewMockStoreProvider(), - failure: fmt.Errorf(sampleWalletErr), - } - - err := CreateProfile(sampleUserID, mockctx, WithKeyServerURL(sampleKeyServerURL)) - require.NoError(t, err) - - wallet, err := New(sampleUserID, mockctx) - require.Error(t, err) - require.Empty(t, wallet) - require.Contains(t, err.Error(), "failed to get wallet content store:") - }) } func TestUpdate(t *testing.T) { @@ -301,6 +305,24 @@ func TestUpdate(t *testing.T) { require.NotEmpty(t, wallet.profile.KeyServerURL) }) + t.Run("test update wallet profile edv settings", func(t *testing.T) { + mockctx := newMockProvider(t) + createSampleProfile(t, mockctx) + + err := UpdateProfile(sampleUserID, mockctx, WithKeyServerURL(sampleKeyServerURL), + WithEDVStorage(sampleEDVServerURL, sampleEDVVaultID, sampleEDVEncryptionKID, sampleEDVMacKID)) + require.NoError(t, err) + + wallet, err := New(sampleUserID, mockctx) + require.NoError(t, err) + require.NotEmpty(t, wallet) + require.NotEmpty(t, wallet.profile.EDVConf) + require.Equal(t, wallet.profile.EDVConf.ServerURL, sampleEDVServerURL) + require.Equal(t, wallet.profile.EDVConf.VaultID, sampleEDVVaultID) + require.Equal(t, wallet.profile.EDVConf.EncryptionKeyID, sampleEDVEncryptionKID) + require.Equal(t, wallet.profile.EDVConf.MACKeyID, sampleEDVMacKID) + }) + t.Run("test update wallet failure", func(t *testing.T) { mockctx := newMockProvider(t) createSampleProfile(t, mockctx) @@ -357,6 +379,16 @@ func TestUpdate(t *testing.T) { require.Empty(t, wallet.profile.KeyServerURL) require.NotEmpty(t, wallet.profile.MasterLockCipher) }) + + t.Run("test update wallet failure - save edv settings error", func(t *testing.T) { + mockctx := newMockProvider(t) + createSampleProfile(t, mockctx) + + err := UpdateProfile(sampleUserID, mockctx, WithKeyServerURL(sampleKeyServerURL), + WithEDVStorage(sampleEDVServerURL, "", sampleEDVEncryptionKID, sampleEDVMacKID)) + require.Error(t, err) + require.Contains(t, err.Error(), "failed to update EDV configuration") + }) } func TestNew(t *testing.T) { @@ -499,10 +531,9 @@ func TestWallet_OpenClose(t *testing.T) { require.NotEmpty(t, wallet) // corrupt content store - wallet.contents, err = newContentStore(&mockstorage.MockStoreProvider{ + wallet.contents = newContentStore(&mockstorage.MockStoreProvider{ ErrOpenStoreHandle: fmt.Errorf(sampleWalletErr), }, wallet.profile) - require.NoError(t, err) // get token token, err := wallet.Open(WithUnlockByAuthorizationToken(sampleRemoteKMSAuth)) @@ -949,6 +980,35 @@ func TestWallet_Issue(t *testing.T) { require.Len(t, result.Proofs, 1) }) + t.Run("Test VC wallet issue JSONWebSignature2020 using controller - success", func(t *testing.T) { + walletInstance, err := New(user, mockctx) + require.NotEmpty(t, walletInstance) + require.NoError(t, err) + + // unlock wallet + authToken, err := walletInstance.Open(WithUnlockByPassphrase(samplePassPhrase)) + require.NoError(t, err) + require.NotEmpty(t, authToken) + + defer walletInstance.Close() + + // import keys manually + kmgr, err := keyManager().getKeyManger(authToken) + require.NoError(t, err) + edPriv := ed25519.PrivateKey(base58.Decode(pkBase58)) + // nolint: errcheck, gosec + kmgr.ImportPrivateKey(edPriv, kms.ED25519, kms.WithKeyID(kid)) + + // sign with just controller + result, err := walletInstance.Issue(authToken, []byte(sampleUDCVC), &ProofOptions{ + Controller: didKey, + ProofType: JSONWebSignature2020, + }) + require.NoError(t, err) + require.NotEmpty(t, result) + require.Len(t, result.Proofs, 1) + }) + t.Run("Test VC wallet issue using verification method - success", func(t *testing.T) { walletInstance, err := New(user, mockctx) require.NotEmpty(t, walletInstance)