diff --git a/component/storage/jsindexeddb/jsindexeddb.go b/component/storage/jsindexeddb/jsindexeddb.go index 66f4f486d6..a7407deb7f 100644 --- a/component/storage/jsindexeddb/jsindexeddb.go +++ b/component/storage/jsindexeddb/jsindexeddb.go @@ -160,8 +160,8 @@ func (s *store) Get(k string) ([]byte, error) { return []byte(data.Get("value").String()), nil } -// Iterator returns iterator for the latest snapshot of the underlying db. -func (s *store) Iterator(start, limit string) storage.StoreIterator { +// Range returns iterator for the latest snapshot of the underlying db. +func (s *store) Range(start, limit string) storage.StoreIterator { if limit == "" { return newIterator(nil, false, fmt.Errorf("limit key is mandatory")) } @@ -197,7 +197,7 @@ func (s *store) Delete(k string) error { } // TODO #2229 - implement query method. -func (s *store) Query(query string) (storage.StoreIterator, error) { +func (s *store) Query(name, value string) (storage.StoreIterator, error) { return nil, storage.ErrQueryingNotSupported } diff --git a/component/storage/leveldb/leveldb_store.go b/component/storage/leveldb/leveldb_store.go index 22f8fd0a4d..0078199786 100644 --- a/component/storage/leveldb/leveldb_store.go +++ b/component/storage/leveldb/leveldb_store.go @@ -112,7 +112,7 @@ type leveldbStore struct { } // TODO #2227 - implement query method. -func (s *leveldbStore) Query(_ string) (storage.StoreIterator, error) { +func (s *leveldbStore) Query(name, value string) (storage.StoreIterator, error) { return nil, storage.ErrQueryingNotSupported } @@ -144,7 +144,7 @@ func (s *leveldbStore) Get(k string) ([]byte, error) { } // Iterator returns iterator for the latest snapshot of the underlying db. -func (s *leveldbStore) Iterator(start, limit string) storage.StoreIterator { +func (s *leveldbStore) Range(start, limit string) storage.StoreIterator { return s.db.NewIterator(&util.Range{ Start: []byte(start), Limit: []byte(strings.ReplaceAll(limit, storage.EndKeySuffix, "~")), diff --git a/pkg/didcomm/protocol/didexchange/service_test.go b/pkg/didcomm/protocol/didexchange/service_test.go index 6c0514afe2..cf83690e40 100644 --- a/pkg/didcomm/protocol/didexchange/service_test.go +++ b/pkg/didcomm/protocol/didexchange/service_test.go @@ -716,7 +716,7 @@ type mockStore struct { put func(string, []byte) error get func(string) ([]byte, error) delete func(string) error - query func(string) (storage.StoreIterator, error) + query func(string, string) (storage.StoreIterator, error) } // Put stores the key and the record. @@ -734,12 +734,12 @@ func (m *mockStore) Delete(k string) error { return m.delete(k) } -func (m *mockStore) Query(query string) (storage.StoreIterator, error) { - return m.query(query) +func (m *mockStore) Query(name, value string) (storage.StoreIterator, error) { + return m.query(name, value) } // Search returns storage iterator. -func (m *mockStore) Iterator(start, limit string) storage.StoreIterator { +func (m *mockStore) Range(start, limit string) storage.StoreIterator { return nil } diff --git a/pkg/didcomm/protocol/introduce/service.go b/pkg/didcomm/protocol/introduce/service.go index 7a26b7881b..6f1bad03e3 100644 --- a/pkg/didcomm/protocol/introduce/service.go +++ b/pkg/didcomm/protocol/introduce/service.go @@ -479,7 +479,7 @@ func (s *Service) currentStateName(piID string) (string, error) { // Actions returns actions for the async usage. func (s *Service) Actions() ([]Action, error) { - records := s.store.Iterator( + records := s.store.Range( fmt.Sprintf(transitionalPayloadKey, ""), fmt.Sprintf(transitionalPayloadKey, storage.EndKeySuffix), ) @@ -708,7 +708,7 @@ func (s *Service) saveParticipant(piID string, p *participant) error { } func (s *Service) getParticipants(piID string) ([]*participant, error) { - records := s.store.Iterator(fmt.Sprintf(participantsKey, piID, ""), + records := s.store.Range(fmt.Sprintf(participantsKey, piID, ""), fmt.Sprintf(participantsKey, piID, storage.EndKeySuffix)) defer records.Release() diff --git a/pkg/didcomm/protocol/issuecredential/service.go b/pkg/didcomm/protocol/issuecredential/service.go index 7e948f87d6..7f2b1b1ff9 100644 --- a/pkg/didcomm/protocol/issuecredential/service.go +++ b/pkg/didcomm/protocol/issuecredential/service.go @@ -565,7 +565,7 @@ func (s *Service) ActionStop(piID string, cErr error) error { // Actions returns actions for the async usage. func (s *Service) Actions() ([]Action, error) { - records := s.store.Iterator( + records := s.store.Range( fmt.Sprintf(transitionalPayloadKey, ""), fmt.Sprintf(transitionalPayloadKey, storage.EndKeySuffix), ) diff --git a/pkg/didcomm/protocol/mediator/service.go b/pkg/didcomm/protocol/mediator/service.go index 42920a5050..306d6b14d5 100644 --- a/pkg/didcomm/protocol/mediator/service.go +++ b/pkg/didcomm/protocol/mediator/service.go @@ -607,7 +607,7 @@ func (s *Service) Unregister(connID string) error { // GetConnections returns the connections of the router. func (s *Service) GetConnections() ([]string, error) { - records := s.routeStore.Iterator( + records := s.routeStore.Range( fmt.Sprintf(routeConnIDDataKey, ""), fmt.Sprintf(routeConnIDDataKey, storage.EndKeySuffix), ) diff --git a/pkg/didcomm/protocol/outofband/service.go b/pkg/didcomm/protocol/outofband/service.go index 02d9e63629..9fbea0ce5f 100644 --- a/pkg/didcomm/protocol/outofband/service.go +++ b/pkg/didcomm/protocol/outofband/service.go @@ -250,7 +250,7 @@ func (s *Service) HandleInbound(msg service.DIDCommMsg, myDID, theirDID string) // Actions returns actions for the async usage. func (s *Service) Actions() ([]Action, error) { - records := s.store.Iterator( + records := s.store.Range( fmt.Sprintf(transitionalPayloadKey, ""), fmt.Sprintf(transitionalPayloadKey, storage.EndKeySuffix), ) diff --git a/pkg/didcomm/protocol/outofband/service_test.go b/pkg/didcomm/protocol/outofband/service_test.go index 9897c88e92..9f6049744f 100644 --- a/pkg/didcomm/protocol/outofband/service_test.go +++ b/pkg/didcomm/protocol/outofband/service_test.go @@ -1265,7 +1265,7 @@ func (s *stubStore) Get(k string) ([]byte, error) { panic("implement me") } -func (s *stubStore) Iterator(start, limit string) storage.StoreIterator { +func (s *stubStore) Range(start, limit string) storage.StoreIterator { panic("implement me") } @@ -1273,7 +1273,7 @@ func (s *stubStore) Delete(k string) error { panic("implement me") } -func (s *stubStore) Query(query string) (storage.StoreIterator, error) { +func (s *stubStore) Query(name, value string) (storage.StoreIterator, error) { panic("implement me") } diff --git a/pkg/didcomm/protocol/presentproof/service.go b/pkg/didcomm/protocol/presentproof/service.go index 832f6a6234..500207ca80 100644 --- a/pkg/didcomm/protocol/presentproof/service.go +++ b/pkg/didcomm/protocol/presentproof/service.go @@ -484,7 +484,7 @@ func (s *Service) deleteTransitionalPayload(id string) error { // Actions returns actions for the async usage. func (s *Service) Actions() ([]Action, error) { - records := s.store.Iterator( + records := s.store.Range( fmt.Sprintf(transitionalPayloadKey, ""), fmt.Sprintf(transitionalPayloadKey, storage.EndKeySuffix), ) diff --git a/pkg/internal/gomocks/storage/mocks.go b/pkg/internal/gomocks/storage/mocks.go index ac1eb41317..e305aaabd4 100644 --- a/pkg/internal/gomocks/storage/mocks.go +++ b/pkg/internal/gomocks/storage/mocks.go @@ -128,20 +128,6 @@ func (mr *MockStoreMockRecorder) Get(arg0 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockStore)(nil).Get), arg0) } -// Iterator mocks base method -func (m *MockStore) Iterator(arg0, arg1 string) storage.StoreIterator { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Iterator", arg0, arg1) - ret0, _ := ret[0].(storage.StoreIterator) - return ret0 -} - -// Iterator indicates an expected call of Iterator -func (mr *MockStoreMockRecorder) Iterator(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Iterator", reflect.TypeOf((*MockStore)(nil).Iterator), arg0, arg1) -} - // Put mocks base method func (m *MockStore) Put(arg0 string, arg1 []byte) error { m.ctrl.T.Helper() @@ -157,16 +143,30 @@ func (mr *MockStoreMockRecorder) Put(arg0, arg1 interface{}) *gomock.Call { } // Query mocks base method -func (m *MockStore) Query(arg0 string) (storage.StoreIterator, error) { +func (m *MockStore) Query(arg0, arg1 string) (storage.StoreIterator, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Query", arg0) + ret := m.ctrl.Call(m, "Query", arg0, arg1) ret0, _ := ret[0].(storage.StoreIterator) ret1, _ := ret[1].(error) return ret0, ret1 } // Query indicates an expected call of Query -func (mr *MockStoreMockRecorder) Query(arg0 interface{}) *gomock.Call { +func (mr *MockStoreMockRecorder) Query(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Query", reflect.TypeOf((*MockStore)(nil).Query), arg0, arg1) +} + +// Range mocks base method +func (m *MockStore) Range(arg0, arg1 string) storage.StoreIterator { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Range", arg0, arg1) + ret0, _ := ret[0].(storage.StoreIterator) + return ret0 +} + +// Range indicates an expected call of Range +func (mr *MockStoreMockRecorder) Range(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Query", reflect.TypeOf((*MockStore)(nil).Query), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Range", reflect.TypeOf((*MockStore)(nil).Range), arg0, arg1) } diff --git a/pkg/mock/storage/mock_store.go b/pkg/mock/storage/mock_store.go index fd17c5d80e..ca557590bb 100644 --- a/pkg/mock/storage/mock_store.go +++ b/pkg/mock/storage/mock_store.go @@ -106,8 +106,8 @@ func (s *MockStore) Get(k string) ([]byte, error) { return val, s.ErrGet } -// Iterator returns an iterator for the underlying mockstore. -func (s *MockStore) Iterator(start, limit string) storage.StoreIterator { +// Range returns an iterator for the underlying mockstore. +func (s *MockStore) Range(start, limit string) storage.StoreIterator { if s.ErrItr != nil { return NewMockIteratorWithError(s.ErrItr) } @@ -136,7 +136,7 @@ func (s *MockStore) Delete(k string) error { } // Query returns a mocked store iterator and error value. -func (s *MockStore) Query(_ string) (storage.StoreIterator, error) { +func (s *MockStore) Query(indexKey, indexValue string) (storage.StoreIterator, error) { return s.QueryReturnValue, s.ErrQuery } diff --git a/pkg/storage/edv/documentprocessor/documentprocessor.go b/pkg/storage/edv/documentprocessor/documentprocessor.go index 72ab980542..524b1b429e 100644 --- a/pkg/storage/edv/documentprocessor/documentprocessor.go +++ b/pkg/storage/edv/documentprocessor/documentprocessor.go @@ -43,13 +43,14 @@ func New(jweEncrypter jose.Encrypter, jweDecrypter jose.Decrypter) *DocumentProc } // Encrypt creates a new encrypted document based off of the given structured document. -func (a *DocumentProcessor) Encrypt(structuredDocument *edv.StructuredDocument) (*edv.EncryptedDocument, error) { - structuredDocumentBytes, err := a.marshal(structuredDocument) +func (d *DocumentProcessor) Encrypt(structuredDocument *edv.StructuredDocument, + indexedAttributes []edv.IndexedAttributeCollection) (*edv.EncryptedDocument, error) { + structuredDocumentBytes, err := d.marshal(structuredDocument) if err != nil { return nil, fmt.Errorf(failMarshalStructuredDocument, err) } - jwe, err := a.jweEncrypter.Encrypt(structuredDocumentBytes) + jwe, err := d.jweEncrypter.Encrypt(structuredDocumentBytes) if err != nil { return nil, fmt.Errorf(failEncryptStructuredDocument, err) } @@ -60,21 +61,22 @@ func (a *DocumentProcessor) Encrypt(structuredDocument *edv.StructuredDocument) } encryptedDoc := edv.EncryptedDocument{ - ID: structuredDocument.ID, - JWE: []byte(serializedJWE), + ID: structuredDocument.ID, + IndexedAttributeCollections: indexedAttributes, + JWE: []byte(serializedJWE), } return &encryptedDoc, nil } // Decrypt decrypts the encrypted document into a structured document. -func (a *DocumentProcessor) Decrypt(encryptedDocument *edv.EncryptedDocument) (*edv.StructuredDocument, error) { +func (d *DocumentProcessor) Decrypt(encryptedDocument *edv.EncryptedDocument) (*edv.StructuredDocument, error) { encryptedJWE, err := jose.Deserialize(string(encryptedDocument.JWE)) if err != nil { return nil, fmt.Errorf(failDeserializeJWE, err) } - structuredDocumentBytes, err := a.jweDecrypter.Decrypt(encryptedJWE) + structuredDocumentBytes, err := d.jweDecrypter.Decrypt(encryptedJWE) if err != nil { return nil, fmt.Errorf(failDecryptJWE, err) } diff --git a/pkg/storage/edv/documentprocessor/documentprocessor_test.go b/pkg/storage/edv/documentprocessor/documentprocessor_test.go index 6645921499..f32965c39a 100644 --- a/pkg/storage/edv/documentprocessor/documentprocessor_test.go +++ b/pkg/storage/edv/documentprocessor/documentprocessor_test.go @@ -43,7 +43,7 @@ func TestAriesDocumentProcessor_Encrypt(t *testing.T) { documentProcessor := DocumentProcessor{marshal: failingMarshal} require.NotNil(t, documentProcessor) - encryptedDocument, err := documentProcessor.Encrypt(nil) + encryptedDocument, err := documentProcessor.Encrypt(nil, nil) require.EqualError(t, err, fmt.Errorf(failMarshalStructuredDocument, errFailingMarshal).Error()) require.Nil(t, encryptedDocument) }) @@ -51,7 +51,7 @@ func TestAriesDocumentProcessor_Encrypt(t *testing.T) { documentProcessor := New(&failingEncrypter{}, nil) require.NotNil(t, documentProcessor) - encryptedDocument, err := documentProcessor.Encrypt(createStructuredDocument()) + encryptedDocument, err := documentProcessor.Encrypt(createStructuredDocument(), nil) require.EqualError(t, err, fmt.Errorf(failEncryptStructuredDocument, errFailingEncrypter).Error()) require.Nil(t, encryptedDocument) }) @@ -145,7 +145,7 @@ func createStructuredDocument() *edv.StructuredDocument { func createEncryptedDocument(t *testing.T, documentProcessor *DocumentProcessor) *edv.EncryptedDocument { structuredDocument := createStructuredDocument() - encryptedDocument, err := documentProcessor.Encrypt(structuredDocument) + encryptedDocument, err := documentProcessor.Encrypt(structuredDocument, nil) require.NoError(t, err) require.NotNil(t, encryptedDocument) diff --git a/pkg/storage/edv/formatprovider/formatprovider.go b/pkg/storage/edv/formatprovider/formatprovider.go index 9dc99144f1..bfa4867557 100644 --- a/pkg/storage/edv/formatprovider/formatprovider.go +++ b/pkg/storage/edv/formatprovider/formatprovider.go @@ -7,49 +7,77 @@ SPDX-License-Identifier: Apache-2.0 package formatprovider import ( + "crypto/rand" + "encoding/base64" "encoding/json" "errors" "fmt" + "github.com/btcsuite/btcutil/base58" + "github.com/hyperledger/aries-framework-go/pkg/storage" "github.com/hyperledger/aries-framework-go/pkg/storage/edv" ) const ( - failOpenUnderlyingStore = "failed to open underlying store in FormatProvider: %w" - failCloseUnderlyingStore = "failed to close underlying store in FormatProvider: %w" - failCloseAllUnderlyingStores = "failed to close all underlying stores in FormatProvider: %w" - failEncryptStructuredDocument = "failed to encrypt structured document into a encrypted document: %w" - failMarshalEncryptedDocument = "failed to marshal encrypted document into bytes: %w" - failPutInUnderlyingStore = "failed to put encrypted document in underlying store in formatStore: %w" - failGetFromUnderlyingStore = "failed to get encrypted document bytes from underlying store in formatStore: %w" + failOpenUnderlyingStore = "failed to open underlying store in FormatProvider: %w" + failCloseUnderlyingStore = "failed to close underlying store in FormatProvider: %w" + failCloseAllUnderlyingStores = "failed to close all underlying stores in FormatProvider: %w" + failGenerateEDVCompatibleID = "failed to generate EDV compatible ID: %w" + failEncryptStructuredDocument = "failed to encrypt structured document into a encrypted document: %w" + failMarshalEncryptedDocument = "failed to marshal encrypted document into bytes: %w" + failPutInUnderlyingStore = "failed to put encrypted document in underlying store in formatStore: %w" + failUnmarshalEncryptedDocument = "failed to unmarshal encrypted document bytes into encrypted document struct: %w" failDecryptEncryptedDocument = "failed to decrypt encrypted document into a structured document: %w" failDeleteInUnderlyingStore = "failed to delete key-value pair in underlying store in formatStore: %w" failQueryUnderlyingStore = "failed to query underlying store in formatStore: %w" - payloadKey = "payload" + + payloadKey = "payload" + keyIndexKey = "indexKey" ) var ( errPayloadKeyMissing = errors.New(`the structured document content did not contain the ` + `expected "payload" key`) - errPayloadNotAssertableAsByteArray = errors.New("unable to assert the payload value as a []byte") + errPayloadNotAssertableAsString = errors.New("unable to assert the payload value as a string") ) type marshalFunc func(interface{}) ([]byte, error) +// MACDigester represents a type that can compute MACs. +type MACDigester interface { + ComputeMAC(data []byte, kh interface{}) ([]byte, error) +} + +// MACCrypto is used for computing MACs. +type MACCrypto struct { + kh interface{} + macDigester MACDigester +} + +// ComputeMAC computes a MAC for data using a matching MAC primitive in kh. +func (m *MACCrypto) ComputeMAC(data string) (string, error) { + dataMAC, err := m.macDigester.ComputeMAC([]byte(data), m.kh) + return string(dataMAC), err +} + // DocumentProcessor represents a type that can encrypt and decrypt between // Structured Documents and Encrypted Documents. type DocumentProcessor interface { - Encrypt(*edv.StructuredDocument) (*edv.EncryptedDocument, error) + Encrypt(*edv.StructuredDocument, []edv.IndexedAttributeCollection) (*edv.EncryptedDocument, error) Decrypt(*edv.EncryptedDocument) (*edv.StructuredDocument, error) } // FormatProvider is an encrypted storage provider that uses EDV document models // as defined in https://identity.foundation/secure-data-store/#data-model. type FormatProvider struct { - storeProvider storage.Provider - documentProcessor DocumentProcessor + storeProvider storage.Provider + documentProcessor DocumentProcessor + macCrypto *MACCrypto + indexKeyMACBase64Encoded string + marshal marshalFunc + generateRandomBytesFunc generateRandomBytesFunc } // New instantiates a new FormatProvider with the given underlying provider and EDV document processor. @@ -57,31 +85,44 @@ type FormatProvider struct { // only deals with data in encrypted form and cannot read the data flowing in or out of it. // The EDV document processor handles encryption/decryption between structured documents and encrypted documents. // It contains the necessary crypto functionality. -func New(underlyingProvider storage.Provider, encryptedDocumentProcessor DocumentProcessor) (FormatProvider, error) { - return FormatProvider{ - storeProvider: underlyingProvider, - documentProcessor: encryptedDocumentProcessor, +func New(underlyingProvider storage.Provider, encryptedDocumentProcessor DocumentProcessor, + macCrypto *MACCrypto) (*FormatProvider, error) { + indexKeyMAC, err := macCrypto.ComputeMAC(keyIndexKey) + if err != nil { + return nil, fmt.Errorf("failed to compute MAC for index name: %w", err) + } + + return &FormatProvider{ + storeProvider: underlyingProvider, + documentProcessor: encryptedDocumentProcessor, + macCrypto: macCrypto, + indexKeyMACBase64Encoded: base64.URLEncoding.EncodeToString([]byte(indexKeyMAC)), + marshal: json.Marshal, + generateRandomBytesFunc: rand.Read, }, nil } // OpenStore opens a store in the underlying provider with the given name and returns a handle to it. -func (p FormatProvider) OpenStore(name string) (storage.Store, error) { +func (p *FormatProvider) OpenStore(name string) (storage.Store, error) { store, err := p.storeProvider.OpenStore(name) if err != nil { return nil, fmt.Errorf(failOpenUnderlyingStore, err) } edvStore := formatStore{ - underlyingStore: store, - documentProcessor: p.documentProcessor, - marshal: json.Marshal, + underlyingStore: store, + documentProcessor: p.documentProcessor, + macCrypto: p.macCrypto, + indexKeyMACBase64Encoded: p.indexKeyMACBase64Encoded, + marshal: p.marshal, + randomBytesFunc: p.generateRandomBytesFunc, } return &edvStore, nil } // CloseStore closes the store with the given name in the underlying provider. -func (p FormatProvider) CloseStore(name string) error { +func (p *FormatProvider) CloseStore(name string) error { err := p.storeProvider.CloseStore(name) if err != nil { return fmt.Errorf(failCloseUnderlyingStore, err) @@ -91,7 +132,7 @@ func (p FormatProvider) CloseStore(name string) error { } // Close closes all stores created in the underlying store provider. -func (p FormatProvider) Close() error { +func (p *FormatProvider) Close() error { err := p.storeProvider.Close() if err != nil { return fmt.Errorf(failCloseAllUnderlyingStores, err) @@ -101,22 +142,34 @@ func (p FormatProvider) Close() error { } type formatStore struct { - underlyingStore storage.Store - documentProcessor DocumentProcessor - - marshal marshalFunc + underlyingStore storage.Store + documentProcessor DocumentProcessor + macCrypto *MACCrypto + indexKeyMACBase64Encoded string + marshal marshalFunc + randomBytesFunc generateRandomBytesFunc } -func (s formatStore) Put(k string, v []byte) error { +func (s *formatStore) Put(k string, v []byte) error { content := make(map[string]interface{}) - content[payloadKey] = v + content[payloadKey] = string(v) + + structuredDocumentID, err := generateEDVCompatibleID(s.randomBytesFunc) + if err != nil { + return fmt.Errorf(failGenerateEDVCompatibleID, err) + } structuredDoc := edv.StructuredDocument{ - ID: k, + ID: structuredDocumentID, Content: content, } - encryptedDoc, err := s.documentProcessor.Encrypt(&structuredDoc) + indexedAttributeCollections, err := s.createIndexedAttribute(k) + if err != nil { + return fmt.Errorf("failed to create indexed attribute: %w", err) + } + + encryptedDoc, err := s.documentProcessor.Encrypt(&structuredDoc, indexedAttributeCollections) if err != nil { return fmt.Errorf(failEncryptStructuredDocument, err) } @@ -134,15 +187,41 @@ func (s formatStore) Put(k string, v []byte) error { return nil } -func (s formatStore) Get(k string) ([]byte, error) { - encryptedDocBytes, err := s.underlyingStore.Get(k) +func (s *formatStore) Get(k string) ([]byte, error) { + indexValueMAC, err := s.macCrypto.ComputeMAC(k) if err != nil { - return nil, fmt.Errorf(failGetFromUnderlyingStore, err) + return nil, fmt.Errorf("failed to compute MAC for index value: %w", err) + } + + matchingDocumentsIterator, err := + s.underlyingStore.Query(s.indexKeyMACBase64Encoded, base64.URLEncoding.EncodeToString([]byte(indexValueMAC))) + if err != nil { + return nil, fmt.Errorf("failed to query underlying store: %w", err) + } + + if !matchingDocumentsIterator.Next() { + return nil, fmt.Errorf("query of underlying store returned no results: %w", storage.ErrDataNotFound) + } + + encryptedDocumentBytes := matchingDocumentsIterator.Value() + + err = matchingDocumentsIterator.Error() + + if err != nil { + return nil, fmt.Errorf("failure while iterating over matching documents: %w", err) + } + + // Ensure that only one document was returned. + // The index name + value pair is supposed to be unique. If multiple documents match the query, something has + // gone very wrong with the database's state. + if matchingDocumentsIterator.Next() { + return nil, errors.New("encrypted index query for document key returned multiple documents." + + " Only one document was expected") } var encryptedDocument edv.EncryptedDocument - err = json.Unmarshal(encryptedDocBytes, &encryptedDocument) + err = json.Unmarshal(encryptedDocumentBytes, &encryptedDocument) if err != nil { return nil, fmt.Errorf(failUnmarshalEncryptedDocument, err) } @@ -157,19 +236,19 @@ func (s formatStore) Get(k string) ([]byte, error) { return nil, errPayloadKeyMissing } - payloadValueBytes, ok := payloadValue.([]byte) + payloadValueString, ok := payloadValue.(string) if !ok { - return nil, errPayloadNotAssertableAsByteArray + return nil, errPayloadNotAssertableAsString } - return payloadValueBytes, nil + return []byte(payloadValueString), nil } -func (s formatStore) Iterator(startKey, endKey string) storage.StoreIterator { - return s.underlyingStore.Iterator(startKey, endKey) +func (s *formatStore) Range(startKey, endKey string) storage.StoreIterator { + return s.underlyingStore.Range(startKey, endKey) } -func (s formatStore) Delete(k string) error { +func (s *formatStore) Delete(k string) error { err := s.underlyingStore.Delete(k) if err != nil { return fmt.Errorf(failDeleteInUnderlyingStore, err) @@ -178,11 +257,48 @@ func (s formatStore) Delete(k string) error { return nil } -func (s formatStore) Query(query string) (storage.StoreIterator, error) { - iterator, err := s.underlyingStore.Query(query) +func (s *formatStore) Query(attributeName, attributeValue string) (storage.StoreIterator, error) { + iterator, err := s.underlyingStore.Query(attributeName, attributeValue) if err != nil { return nil, fmt.Errorf(failQueryUnderlyingStore, err) } return iterator, err } + +type generateRandomBytesFunc func([]byte) (int, error) + +func generateEDVCompatibleID(generateRandomBytes generateRandomBytesFunc) (string, error) { + randomBytes := make([]byte, 16) + + _, err := generateRandomBytes(randomBytes) + if err != nil { + return "", err + } + + base58EncodedUUID := base58.Encode(randomBytes) + + return base58EncodedUUID, nil +} + +func (s *formatStore) createIndexedAttribute(k string) ([]edv.IndexedAttributeCollection, error) { + indexValueMAC, err := s.macCrypto.ComputeMAC(k) + if err != nil { + return nil, fmt.Errorf("failed to compute MAC for index value: %w", err) + } + + indexedAttribute := edv.IndexedAttribute{ + Name: s.indexKeyMACBase64Encoded, + Value: base64.URLEncoding.EncodeToString([]byte(indexValueMAC)), + Unique: true, + } + + indexedAttributeCollection := edv.IndexedAttributeCollection{ + HMAC: edv.IDTypePair{}, + IndexedAttributes: []edv.IndexedAttribute{indexedAttribute}, + } + + indexedAttributeCollections := []edv.IndexedAttributeCollection{indexedAttributeCollection} + + return indexedAttributeCollections, nil +} diff --git a/pkg/storage/edv/formatprovider/formatprovider_test.go b/pkg/storage/edv/formatprovider/formatprovider_test.go index 4a25f3ecfb..ec85a2a2ce 100644 --- a/pkg/storage/edv/formatprovider/formatprovider_test.go +++ b/pkg/storage/edv/formatprovider/formatprovider_test.go @@ -7,26 +7,38 @@ SPDX-License-Identifier: Apache-2.0 package formatprovider import ( + "bytes" + "encoding/json" "errors" "fmt" "testing" + "github.com/google/tink/go/keyset" + "github.com/google/tink/go/mac" "github.com/stretchr/testify/require" + "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto" + "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite" + "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite/ecdhes" + "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto/primitive/composite/keyio" + "github.com/hyperledger/aries-framework-go/pkg/doc/jose" mockstorage "github.com/hyperledger/aries-framework-go/pkg/mock/storage" "github.com/hyperledger/aries-framework-go/pkg/storage/edv" + "github.com/hyperledger/aries-framework-go/pkg/storage/edv/documentprocessor" "github.com/hyperledger/aries-framework-go/pkg/storage/mem" ) func TestNew(t *testing.T) { - provider, err := New(mem.NewProvider(), &mockDocumentProcessor{}) - require.NoError(t, err) - require.NotNil(t, provider) + t.Run("Success", func(t *testing.T) { + provider, err := New(mem.NewProvider(), createDocumentProcessor(t), newMACCrypto(t)) + require.NoError(t, err) + require.NotNil(t, provider) + }) } func TestFormatProvider_OpenStore(t *testing.T) { t.Run("Success", func(t *testing.T) { - provider, err := New(mem.NewProvider(), &mockDocumentProcessor{}) + provider, err := New(mem.NewProvider(), createDocumentProcessor(t), newMACCrypto(t)) require.NoError(t, err) require.NotNil(t, provider) @@ -39,7 +51,7 @@ func TestFormatProvider_OpenStore(t *testing.T) { mockStoreProvider := mockstorage.MockStoreProvider{ErrOpenStoreHandle: errTest} - provider, err := New(&mockStoreProvider, &mockDocumentProcessor{}) + provider, err := New(&mockStoreProvider, createDocumentProcessor(t), newMACCrypto(t)) require.NoError(t, err) require.NotNil(t, provider) @@ -51,7 +63,7 @@ func TestFormatProvider_OpenStore(t *testing.T) { func TestFormatProvider_CloseStore(t *testing.T) { t.Run("Success", func(t *testing.T) { - provider, err := New(mem.NewProvider(), &mockDocumentProcessor{}) + provider, err := New(mem.NewProvider(), createDocumentProcessor(t), newMACCrypto(t)) require.NoError(t, err) require.NotNil(t, provider) @@ -64,7 +76,7 @@ func TestFormatProvider_CloseStore(t *testing.T) { mockStoreProvider := mockstorage.NewMockStoreProvider() mockStoreProvider.ErrCloseStore = errTest - provider, err := New(mockStoreProvider, &mockDocumentProcessor{}) + provider, err := New(mockStoreProvider, createDocumentProcessor(t), newMACCrypto(t)) require.NoError(t, err) require.NotNil(t, provider) @@ -75,7 +87,7 @@ func TestFormatProvider_CloseStore(t *testing.T) { func TestFormatProvider_Close(t *testing.T) { t.Run("Success", func(t *testing.T) { - provider, err := New(mem.NewProvider(), &mockDocumentProcessor{}) + provider, err := New(mem.NewProvider(), createDocumentProcessor(t), newMACCrypto(t)) require.NoError(t, err) require.NotNil(t, provider) @@ -88,7 +100,7 @@ func TestFormatProvider_Close(t *testing.T) { mockStoreProvider := mockstorage.NewMockStoreProvider() mockStoreProvider.ErrClose = errTest - provider, err := New(mockStoreProvider, &mockDocumentProcessor{}) + provider, err := New(mockStoreProvider, createDocumentProcessor(t), newMACCrypto(t)) require.NoError(t, err) require.NotNil(t, provider) @@ -99,7 +111,7 @@ func TestFormatProvider_Close(t *testing.T) { func Test_formatStore_Put(t *testing.T) { t.Run("Success", func(t *testing.T) { - provider, err := New(mem.NewProvider(), &mockDocumentProcessor{}) + provider, err := New(mem.NewProvider(), createDocumentProcessor(t), newMACCrypto(t)) require.NoError(t, err) require.NotNil(t, provider) @@ -110,10 +122,23 @@ func Test_formatStore_Put(t *testing.T) { err = store.Put("key", []byte("data")) require.NoError(t, err) }) + t.Run("Fail to generate EDV compatible ID", func(t *testing.T) { + provider, err := New(mem.NewProvider(), createDocumentProcessor(t), newMACCrypto(t)) + require.NoError(t, err) + require.NotNil(t, provider) + provider.generateRandomBytesFunc = failingGenerateRandomBytesFunc + + store, err := provider.OpenStore("testName") + require.NoError(t, err) + require.NotNil(t, store) + + err = store.Put("key", []byte("data")) + require.EqualError(t, err, fmt.Errorf(failGenerateEDVCompatibleID, errGenerateRandomBytes).Error()) + }) t.Run("Fail to encrypt structured document", func(t *testing.T) { errTest := errors.New("test error") - provider, err := New(mem.NewProvider(), &mockDocumentProcessor{errEncrypt: errTest}) + provider, err := New(mem.NewProvider(), &mockDocumentProcessor{errEncrypt: errTest}, newMACCrypto(t)) require.NoError(t, err) require.NotNil(t, provider) @@ -125,18 +150,15 @@ func Test_formatStore_Put(t *testing.T) { require.EqualError(t, err, fmt.Errorf(failEncryptStructuredDocument, errTest).Error()) }) t.Run("Fail to marshal encrypted document", func(t *testing.T) { - provider, err := New(mem.NewProvider(), &mockDocumentProcessor{}) + provider, err := New(mem.NewProvider(), createDocumentProcessor(t), newMACCrypto(t)) require.NoError(t, err) require.NotNil(t, provider) + provider.marshal = failingMarshal store, err := provider.OpenStore("testName") require.NoError(t, err) require.NotNil(t, store) - fmtStore, ok := store.(*formatStore) - require.True(t, ok, "Failed to assert store as an *formatStore") - fmtStore.marshal = failingMarshal - err = store.Put("key", []byte("data")) require.EqualError(t, err, fmt.Errorf(failMarshalEncryptedDocument, errFailingMarshal).Error()) }) @@ -146,7 +168,7 @@ func Test_formatStore_Put(t *testing.T) { mockStoreProvider := mockstorage.NewMockStoreProvider() mockStoreProvider.Store.ErrPut = errTest - provider, err := New(mockStoreProvider, &mockDocumentProcessor{}) + provider, err := New(mockStoreProvider, createDocumentProcessor(t), newMACCrypto(t)) require.NoError(t, err) require.NotNil(t, provider) @@ -161,7 +183,7 @@ func Test_formatStore_Put(t *testing.T) { func Test_formatStore_Get(t *testing.T) { t.Run("Success", func(t *testing.T) { - provider, err := New(mem.NewProvider(), &mockDocumentProcessor{}) + provider, err := New(mem.NewProvider(), createDocumentProcessor(t), newMACCrypto(t)) require.NoError(t, err) require.NotNil(t, provider) @@ -176,13 +198,12 @@ func Test_formatStore_Get(t *testing.T) { require.NoError(t, err) require.Equal(t, "data", string(value)) }) - t.Run("Fail to get encrypted document bytes from underlying store", func(t *testing.T) { - errTest := errors.New("test error") - + t.Run("Fail to unmarshal encrypted document", func(t *testing.T) { + mockIteratorBatch := [][]string{{"key", "Not a valid Encrypted Document!"}} mockStoreProvider := mockstorage.NewMockStoreProvider() - mockStoreProvider.Store.ErrGet = errTest + mockStoreProvider.Store.QueryReturnValue = mockstorage.NewMockIterator(mockIteratorBatch) - provider, err := New(mockStoreProvider, &mockDocumentProcessor{}) + provider, err := New(mockStoreProvider, &mockDocumentProcessor{}, newMACCrypto(t)) require.NoError(t, err) require.NotNil(t, provider) @@ -190,29 +211,6 @@ func Test_formatStore_Get(t *testing.T) { require.NoError(t, err) require.NotNil(t, store) - err = store.Put("key", []byte("data")) - require.NoError(t, err) - - value, err := store.Get("key") - require.EqualError(t, err, fmt.Errorf(failGetFromUnderlyingStore, errTest).Error()) - require.Nil(t, value) - }) - t.Run("Fail to unmarshal encrypted document", func(t *testing.T) { - provider, err := New(mem.NewProvider(), &mockDocumentProcessor{}) - require.NoError(t, err) - require.NotNil(t, provider) - - store, err := provider.OpenStore("testName") - require.NoError(t, err) - require.NotNil(t, store) - - fmtStore, ok := store.(*formatStore) - require.True(t, ok, "Failed to assert store as an *formatStore") - fmtStore.marshal = failingMarshal - - err = fmtStore.underlyingStore.Put("key", []byte("Not a valid Encrypted Document!")) - require.NoError(t, err) - value, err := store.Get("key") require.EqualError(t, err, fmt.Errorf(failUnmarshalEncryptedDocument, @@ -220,9 +218,18 @@ func Test_formatStore_Get(t *testing.T) { require.Nil(t, value) }) t.Run("Fail to decrypt encrypted document", func(t *testing.T) { + encryptedDocument := edv.EncryptedDocument{} + + encryptedDocumentBytes, err := json.Marshal(encryptedDocument) + require.NoError(t, err) + + mockIteratorBatch := [][]string{{"key", string(encryptedDocumentBytes)}} + mockStoreProvider := mockstorage.NewMockStoreProvider() + mockStoreProvider.Store.QueryReturnValue = mockstorage.NewMockIterator(mockIteratorBatch) + errTest := errors.New("test error") - provider, err := New(mem.NewProvider(), &mockDocumentProcessor{errDecrypt: errTest}) + provider, err := New(mockStoreProvider, &mockDocumentProcessor{errDecrypt: errTest}, newMACCrypto(t)) require.NoError(t, err) require.NotNil(t, provider) @@ -230,16 +237,22 @@ func Test_formatStore_Get(t *testing.T) { require.NoError(t, err) require.NotNil(t, store) - err = store.Put("key", []byte("data")) - require.NoError(t, err) - value, err := store.Get("key") require.EqualError(t, err, fmt.Errorf(failDecryptEncryptedDocument, errTest).Error()) require.Nil(t, value) }) t.Run("Structured document is missing the payload key", func(t *testing.T) { - provider, err := New(mem.NewProvider(), - &mockDocumentProcessor{structuredDocToReturnOnDecrypt: &edv.StructuredDocument{}}) + encryptedDocument := edv.EncryptedDocument{} + + encryptedDocumentBytes, err := json.Marshal(encryptedDocument) + require.NoError(t, err) + + mockIteratorBatch := [][]string{{"key", string(encryptedDocumentBytes)}} + mockStoreProvider := mockstorage.NewMockStoreProvider() + mockStoreProvider.Store.QueryReturnValue = mockstorage.NewMockIterator(mockIteratorBatch) + + provider, err := New(mockStoreProvider, + &mockDocumentProcessor{structuredDocToReturnOnDecrypt: &edv.StructuredDocument{}}, newMACCrypto(t)) require.NoError(t, err) require.NotNil(t, provider) @@ -254,15 +267,21 @@ func Test_formatStore_Get(t *testing.T) { require.EqualError(t, err, errPayloadKeyMissing.Error()) require.Nil(t, value) }) - t.Run("Structured document payload cannot be asserted as []byte", func(t *testing.T) { + t.Run("Structured document payload cannot be asserted as string", func(t *testing.T) { + mockIteratorBatch := [][]string{{"key", getBarebonesMarshalledEncryptedDocument(t)}} + mockStoreProvider := mockstorage.NewMockStoreProvider() + mockStoreProvider.Store.QueryReturnValue = mockstorage.NewMockIterator(mockIteratorBatch) + content := make(map[string]interface{}) - content["payload"] = "not a []byte" + content["payload"] = []byte("data") - unexpectedStructuredDocument := &edv.StructuredDocument{ + structuredDocumentWithByteArrayPayload := &edv.StructuredDocument{ Content: content, } - provider, err := New(mem.NewProvider(), - &mockDocumentProcessor{structuredDocToReturnOnDecrypt: unexpectedStructuredDocument}) + + provider, err := New(mockStoreProvider, + &mockDocumentProcessor{structuredDocToReturnOnDecrypt: structuredDocumentWithByteArrayPayload}, + newMACCrypto(t)) require.NoError(t, err) require.NotNil(t, provider) @@ -270,18 +289,24 @@ func Test_formatStore_Get(t *testing.T) { require.NoError(t, err) require.NotNil(t, store) - err = store.Put("key", []byte("data")) - require.NoError(t, err) - value, err := store.Get("key") - require.EqualError(t, err, errPayloadNotAssertableAsByteArray.Error()) + require.EqualError(t, err, errPayloadNotAssertableAsString.Error()) require.Nil(t, value) }) } +func getBarebonesMarshalledEncryptedDocument(t *testing.T) string { + encryptedDocument := edv.EncryptedDocument{} + + encryptedDocumentBytes, err := json.Marshal(encryptedDocument) + require.NoError(t, err) + + return string(encryptedDocumentBytes) +} + func Test_formatStore_Iterator(t *testing.T) { t.Run("Success", func(t *testing.T) { - provider, err := New(mem.NewProvider(), &mockDocumentProcessor{}) + provider, err := New(mem.NewProvider(), createDocumentProcessor(t), newMACCrypto(t)) require.NoError(t, err) require.NotNil(t, provider) @@ -289,14 +314,14 @@ func Test_formatStore_Iterator(t *testing.T) { require.NoError(t, err) require.NotNil(t, store) - iterator := store.Iterator("", "") + iterator := store.Range("", "") require.NotNil(t, iterator) }) } func Test_formatStore_Delete(t *testing.T) { t.Run("Success", func(t *testing.T) { - provider, err := New(mem.NewProvider(), &mockDocumentProcessor{}) + provider, err := New(mem.NewProvider(), createDocumentProcessor(t), newMACCrypto(t)) require.NoError(t, err) require.NotNil(t, provider) @@ -313,7 +338,7 @@ func Test_formatStore_Delete(t *testing.T) { mockStoreProvider := mockstorage.NewMockStoreProvider() mockStoreProvider.Store.ErrDelete = errTest - provider, err := New(mockStoreProvider, &mockDocumentProcessor{}) + provider, err := New(mockStoreProvider, createDocumentProcessor(t), newMACCrypto(t)) require.NoError(t, err) require.NotNil(t, provider) @@ -331,7 +356,7 @@ func Test_formatStore_Query(t *testing.T) { mockStoreProvider := mockstorage.NewMockStoreProvider() mockStoreProvider.Store.QueryReturnValue = mockstorage.NewMockIterator(nil) - provider, err := New(mockStoreProvider, &mockDocumentProcessor{}) + provider, err := New(mockStoreProvider, createDocumentProcessor(t), newMACCrypto(t)) require.NoError(t, err) require.NotNil(t, provider) @@ -339,7 +364,7 @@ func Test_formatStore_Query(t *testing.T) { require.NoError(t, err) require.NotNil(t, store) - iterator, err := store.Query("query") + iterator, err := store.Query("", "") require.NoError(t, err) require.NotNil(t, iterator) }) @@ -349,7 +374,7 @@ func Test_formatStore_Query(t *testing.T) { mockStoreProvider := mockstorage.NewMockStoreProvider() mockStoreProvider.Store.ErrQuery = errTest - provider, err := New(mockStoreProvider, &mockDocumentProcessor{}) + provider, err := New(mockStoreProvider, createDocumentProcessor(t), newMACCrypto(t)) require.NoError(t, err) require.NotNil(t, provider) @@ -357,7 +382,7 @@ func Test_formatStore_Query(t *testing.T) { require.NoError(t, err) require.NotNil(t, store) - iterator, err := store.Query("query") + iterator, err := store.Query("", "") require.EqualError(t, err, fmt.Errorf(failQueryUnderlyingStore, errTest).Error()) require.Nil(t, iterator) }) @@ -369,31 +394,70 @@ type mockDocumentProcessor struct { structuredDocToReturnOnDecrypt *edv.StructuredDocument } -func (m *mockDocumentProcessor) Encrypt(*edv.StructuredDocument) (*edv.EncryptedDocument, error) { +func (m *mockDocumentProcessor) Encrypt(*edv.StructuredDocument, + []edv.IndexedAttributeCollection) (*edv.EncryptedDocument, error) { return &edv.EncryptedDocument{}, m.errEncrypt } func (m *mockDocumentProcessor) Decrypt(*edv.EncryptedDocument) (*edv.StructuredDocument, error) { - var structuredDocToReturn *edv.StructuredDocument + return m.structuredDocToReturnOnDecrypt, m.errDecrypt +} - if m.structuredDocToReturnOnDecrypt != nil { - structuredDocToReturn = m.structuredDocToReturnOnDecrypt - } else { - content := make(map[string]interface{}) - content["payload"] = []byte("data") +var errFailingMarshal = errors.New("failingMarshal always fails") - structuredDoc := edv.StructuredDocument{ - Content: content, - } +func failingMarshal(interface{}) ([]byte, error) { + return nil, errFailingMarshal +} - structuredDocToReturn = &structuredDoc - } +var errGenerateRandomBytes = errors.New("failingGenerateRandomBytesFunc always fails") - return structuredDocToReturn, m.errDecrypt +func failingGenerateRandomBytesFunc([]byte) (int, error) { + return -1, errGenerateRandomBytes } -var errFailingMarshal = errors.New("failingMarshal always fails") +func createDocumentProcessor(t *testing.T) DocumentProcessor { + encrypter, decrypter := createEncrypterAndDecrypter(t) -func failingMarshal(interface{}) ([]byte, error) { - return nil, errFailingMarshal + documentProcessor := documentprocessor.New(encrypter, decrypter) + require.NotNil(t, documentProcessor) + + return documentProcessor +} + +func createEncrypterAndDecrypter(t *testing.T) (*jose.JWEEncrypt, *jose.JWEDecrypt) { + keyHandle, err := keyset.NewHandle(ecdhes.ECDHES256KWAES256GCMKeyTemplate()) + require.NoError(t, err) + + pubKH, err := keyHandle.Public() + require.NoError(t, err) + + buf := new(bytes.Buffer) + pubKeyWriter := keyio.NewWriter(buf) + + err = pubKH.WriteWithNoSecrets(pubKeyWriter) + require.NoError(t, err) + + ecPubKey := new(composite.PublicKey) + + err = json.Unmarshal(buf.Bytes(), ecPubKey) + require.NoError(t, err) + + encrypter, err := jose.NewJWEEncrypt(jose.A256GCM, "EDVEncryptedDocument", "", nil, + []*composite.PublicKey{ecPubKey}) + require.NoError(t, err) + + decrypter := jose.NewJWEDecrypt(nil, keyHandle) + + return encrypter, decrypter +} + +func newMACCrypto(t *testing.T) *MACCrypto { + crypto, err := tinkcrypto.New() + require.NoError(t, err) + + kh, err := keyset.NewHandle(mac.HMACSHA256Tag256KeyTemplate()) + require.NoError(t, err) + require.NotNil(t, kh) + + return &MACCrypto{macDigester: crypto, kh: kh} } diff --git a/pkg/storage/edv/models.go b/pkg/storage/edv/models.go index 0a61c230c4..f80a22f02a 100644 --- a/pkg/storage/edv/models.go +++ b/pkg/storage/edv/models.go @@ -44,6 +44,8 @@ type IndexedAttribute struct { } // IDTypePair represents an ID+Type pair. +// TODO: #2262 This is a simplified version of the actual EDV query format, which is still not finalized +// in the spec as of writing. Spec: https://identity.foundation/secure-data-store/#creating-encrypted-indexes. type IDTypePair struct { ID string `json:"id"` Type string `json:"type"` diff --git a/pkg/storage/mem/mem_store.go b/pkg/storage/mem/mem_store.go index 1211ec8b7b..7a28d21781 100644 --- a/pkg/storage/mem/mem_store.go +++ b/pkg/storage/mem/mem_store.go @@ -127,7 +127,7 @@ func (s *memStore) Get(k string) ([]byte, error) { } // Iterator returns iterator for the latest snapshot of the underlying db. -func (s *memStore) Iterator(start, limit string) storage.StoreIterator { +func (s *memStore) Range(start, limit string) storage.StoreIterator { if limit == "" { return newMemIterator(nil) } @@ -191,9 +191,20 @@ func (s *memStore) Delete(k string) error { return nil } -// TODO #2228 - implement query method. -func (s *memStore) Query(_ string) (storage.StoreIterator, error) { - return nil, storage.ErrQueryingNotSupported +// A simple query method useful for testing purposes. Not necessarily performant or good for production code. +// The iterator will iterate over all key-value pairs from the memStore database that satisfy the query. +// A key-value pair from the memStore database satisfies the query if the value contains both indexKey and indexValue +// somewhere in the value. +func (s *memStore) Query(name, value string) (storage.StoreIterator, error) { + var results [][]string + + for k, v := range s.db { + if strings.Contains(string(v), name) && strings.Contains(string(v), value) { + results = append(results, []string{k, string(v)}) + } + } + + return newMemIterator(results), nil } type memIterator struct { diff --git a/pkg/storage/mem/mem_store_test.go b/pkg/storage/mem/mem_store_test.go index e02436f986..95d67b1b37 100644 --- a/pkg/storage/mem/mem_store_test.go +++ b/pkg/storage/mem/mem_store_test.go @@ -206,19 +206,19 @@ func TestMemStore(t *testing.T) { require.NoError(t, err) } - itr := store.Iterator("abc_", "abc_"+storage.EndKeySuffix) + itr := store.Range("abc_", "abc_"+storage.EndKeySuffix) verifyItr(t, itr, 4, "abc_") - itr = store.Iterator("", "") + itr = store.Range("", "") verifyItr(t, itr, 0, "") - itr = store.Iterator("abc_", "mno_"+storage.EndKeySuffix) + itr = store.Range("abc_", "mno_"+storage.EndKeySuffix) verifyItr(t, itr, 7, "") - itr = store.Iterator("abc_", "mno_123") + itr = store.Range("abc_", "mno_123") verifyItr(t, itr, 6, "") - itr = store.Iterator("t_", "t_"+storage.EndKeySuffix) + itr = store.Range("t_", "t_"+storage.EndKeySuffix) verifyItr(t, itr, 0, "") }) } diff --git a/pkg/storage/store.go b/pkg/storage/store.go index 3336c71bf0..1d2d8184ad 100644 --- a/pkg/storage/store.go +++ b/pkg/storage/store.go @@ -54,14 +54,13 @@ type Store interface { // Returns: // // StoreIterator: iterator for result range - Iterator(startKey, endKey string) StoreIterator + Range(startKey, endKey string) StoreIterator // Delete will delete a record with k key Delete(k string) error - // Query queries the store for data based on the provided query string, the format of - // which will be dependent on what the underlying store requires. - Query(query string) (StoreIterator, error) + // Query searches all stored key-value pairs and returns all values that satisfy the query. + Query(name string, value string) (StoreIterator, error) } // StoreIterator is the iterator for the latest snapshot of the underlying store. diff --git a/pkg/storage/wrapper/prefix/prefix_wrapper.go b/pkg/storage/wrapper/prefix/prefix_wrapper.go index 08caf06f1a..c6b17e74ec 100644 --- a/pkg/storage/wrapper/prefix/prefix_wrapper.go +++ b/pkg/storage/wrapper/prefix/prefix_wrapper.go @@ -52,7 +52,7 @@ func (b *StorePrefixWrapper) Get(k string) ([]byte, error) { return b.store.Get(k) } -// Iterator returns an iterator for the latest snapshot of the underlying store. +// Range returns an iterator for the latest snapshot of the underlying store. // // Args: // @@ -62,7 +62,7 @@ func (b *StorePrefixWrapper) Get(k string) ([]byte, error) { // Returns: // // StoreIterator: a wrapped iterator for result range. -func (b *StorePrefixWrapper) Iterator(startKey, endKey string) storage.StoreIterator { +func (b *StorePrefixWrapper) Range(startKey, endKey string) storage.StoreIterator { if startKey != "" { startKey = b.prefix + startKey } @@ -71,7 +71,7 @@ func (b *StorePrefixWrapper) Iterator(startKey, endKey string) storage.StoreIter endKey = b.prefix + endKey } - return b.store.Iterator(startKey, endKey) + return b.store.Range(startKey, endKey) } // Delete will delete a record with k by prefixing it with IDPrefix first. @@ -84,6 +84,6 @@ func (b *StorePrefixWrapper) Delete(k string) error { } // Query is currently not implemented. -func (b *StorePrefixWrapper) Query(_ string) (storage.StoreIterator, error) { +func (b *StorePrefixWrapper) Query(name, value string) (storage.StoreIterator, error) { return nil, storage.ErrQueryingNotSupported } diff --git a/pkg/storage/wrapper/prefix/prefix_wrapper_test.go b/pkg/storage/wrapper/prefix/prefix_wrapper_test.go index be284f916a..6858838990 100644 --- a/pkg/storage/wrapper/prefix/prefix_wrapper_test.go +++ b/pkg/storage/wrapper/prefix/prefix_wrapper_test.go @@ -184,16 +184,16 @@ func TestStorePrefixWrapper(t *testing.T) { require.NoError(t, err) } - itr := store.Iterator("abc_", "abc_"+storage.EndKeySuffix) + itr := store.Range("abc_", "abc_"+storage.EndKeySuffix) verifyItr(t, itr, 4, cdbPrefix+"abc_") - itr = store.Iterator("", "") + itr = store.Range("", "") verifyItr(t, itr, 0, "") - itr = store.Iterator("abc_", "mno_"+storage.EndKeySuffix) + itr = store.Range("abc_", "mno_"+storage.EndKeySuffix) verifyItr(t, itr, 7, "") - itr = store.Iterator("abc_", "mno_123") + itr = store.Range("abc_", "mno_123") verifyItr(t, itr, 6, "") }) } diff --git a/pkg/store/connection/connection_lookup.go b/pkg/store/connection/connection_lookup.go index 929a077346..f84f2aaeb4 100644 --- a/pkg/store/connection/connection_lookup.go +++ b/pkg/store/connection/connection_lookup.go @@ -106,7 +106,7 @@ func (c *Lookup) QueryConnectionRecords() ([]*Record, error) { // TODO https://github.com/hyperledger/aries-framework-go/issues/655 query criteria to be added as part of issue searchKey := getConnectionKeyPrefix()("") - itr := c.store.Iterator(searchKey, fmt.Sprintf(limitPattern, searchKey)) + itr := c.store.Range(searchKey, fmt.Sprintf(limitPattern, searchKey)) defer itr.Release() var records []*Record @@ -126,7 +126,7 @@ func (c *Lookup) QueryConnectionRecords() ([]*Record, error) { records = append(records, &record) } - protocolStateItr := c.protocolStateStore.Iterator(searchKey, fmt.Sprintf(limitPattern, searchKey)) + protocolStateItr := c.protocolStateStore.Range(searchKey, fmt.Sprintf(limitPattern, searchKey)) defer protocolStateItr.Release() for protocolStateItr.Next() { diff --git a/pkg/store/connection/connection_recorder.go b/pkg/store/connection/connection_recorder.go index 0f01d2fd50..73d4186a05 100644 --- a/pkg/store/connection/connection_recorder.go +++ b/pkg/store/connection/connection_recorder.go @@ -204,7 +204,7 @@ func computeHash(bytes []byte) (string, error) { } func removeConnectionsForStates(c *Recorder, connectionID string) error { - itr := c.protocolStateStore.Iterator(getConnectionStateKeyPrefix()( + itr := c.protocolStateStore.Range(getConnectionStateKeyPrefix()( connectionID), getConnectionStateKeyPrefix()(connectionID)+storage.EndKeySuffix, ) diff --git a/pkg/store/connection/connection_recorder_test.go b/pkg/store/connection/connection_recorder_test.go index 8e6c3f4429..84e7940637 100644 --- a/pkg/store/connection/connection_recorder_test.go +++ b/pkg/store/connection/connection_recorder_test.go @@ -506,7 +506,7 @@ func TestConnectionRecorder_RemoveConnection(t *testing.T) { require.Error(t, err) require.Contains(t, err.Error(), "data not found") - itr := recorder.protocolStateStore.Iterator( + itr := recorder.protocolStateStore.Range( getConnectionStateKeyPrefix()(record.ConnectionID), getConnectionStateKeyPrefix()(record.ConnectionID)+storage.EndKeySuffix, ) diff --git a/pkg/store/did/store.go b/pkg/store/did/store.go index e800790826..6f5a131a00 100644 --- a/pkg/store/did/store.go +++ b/pkg/store/did/store.go @@ -103,7 +103,7 @@ func (s *Store) GetDIDByName(name string) (string, error) { func (s *Store) GetDIDRecords() []*Record { searchKey := didNameDataKey("") - itr := s.store.Iterator(searchKey, fmt.Sprintf(limitPattern, searchKey)) + itr := s.store.Range(searchKey, fmt.Sprintf(limitPattern, searchKey)) defer itr.Release() var records []*Record diff --git a/pkg/store/verifiable/store.go b/pkg/store/verifiable/store.go index 47a692fd1d..1655235417 100644 --- a/pkg/store/verifiable/store.go +++ b/pkg/store/verifiable/store.go @@ -316,7 +316,7 @@ func (s *StoreImplementation) remove(id, recordKey string) error { } func (s *StoreImplementation) getAllRecords(searchKey string) ([]*Record, error) { - itr := s.store.Iterator(searchKey, fmt.Sprintf(limitPattern, searchKey)) + itr := s.store.Range(searchKey, fmt.Sprintf(limitPattern, searchKey)) defer itr.Release() var records []*Record