Skip to content
This repository has been archived by the owner on Mar 27, 2024. It is now read-only.

Commit

Permalink
feat: Enforce EDV document ID format in EDVFormatProvider
Browse files Browse the repository at this point in the history
Signed-off-by: Derek Trider <Derek.Trider@securekey.com>
  • Loading branch information
Derek Trider committed Oct 21, 2020
1 parent 5e3788f commit 57eeefe
Show file tree
Hide file tree
Showing 11 changed files with 138 additions and 36 deletions.
2 changes: 1 addition & 1 deletion component/storage/leveldb/leveldb_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ type leveldbStore struct {
}

// TODO #2227 - implement query method.
func (s *leveldbStore) Query(_ string) (storage.StoreIterator, error) {
func (s *leveldbStore) Query(indexKey string, indexValue string) (storage.StoreIterator, error) {
return nil, storage.ErrQueryingNotSupported
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/didcomm/protocol/didexchange/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -734,7 +734,7 @@ func (m *mockStore) Delete(k string) error {
return m.delete(k)
}

func (m *mockStore) Query(query string) (storage.StoreIterator, error) {
func (m *mockStore) Query(indexKey string, indexValue string) (storage.StoreIterator, error) {
return m.query(query)
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/didcomm/protocol/outofband/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(indexKey string, indexValue string) (storage.StoreIterator, error) {
panic("implement me")
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/mock/storage/mock_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 string, indexValue string) (storage.StoreIterator, error) {
return s.QueryReturnValue, s.ErrQuery
}

Expand Down
15 changes: 8 additions & 7 deletions pkg/storage/edv/documentprocessor/documentprocessor.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ 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 := json.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)
}
Expand All @@ -60,21 +60,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)
}
Expand Down
136 changes: 118 additions & 18 deletions pkg/storage/edv/formatprovider/formatprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ SPDX-License-Identifier: Apache-2.0
package formatprovider

import (
"crypto/rand"
"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"
)
Expand All @@ -27,7 +30,9 @@ const (
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 = "key"
)

var (
Expand All @@ -38,10 +43,24 @@ var (

type marshalFunc func(interface{}) ([]byte, error)

type MACComputer interface {
ComputeMAC(data []byte, kh interface{}) ([]byte, error)
}

type MACCrypto struct {
kh interface{}
macComputer MACComputer
}

func (m *MACCrypto) ComputeMAC(data string) (string, error) {
dataMAC, err := m.macComputer.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)
}

Expand All @@ -50,22 +69,24 @@ type DocumentProcessor interface {
type FormatProvider struct {
storeProvider storage.Provider
documentProcessor DocumentProcessor
macCrypto *MACCrypto
}

// New instantiates a new FormatProvider with the given underlying provider and EDV document processor.
// The underlying store provider determines where/how the data (in EDV Encrypted Document format) is actually stored. It
// 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{
func New(underlyingProvider storage.Provider, encryptedDocumentProcessor DocumentProcessor, macCrypto *MACCrypto) (*FormatProvider, error) {
return &FormatProvider{
storeProvider: underlyingProvider,
documentProcessor: encryptedDocumentProcessor,
macCrypto: macCrypto,
}, 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)
Expand All @@ -81,7 +102,7 @@ func (p FormatProvider) OpenStore(name string) (storage.Store, error) {
}

// 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)
Expand All @@ -91,7 +112,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)
Expand All @@ -103,20 +124,49 @@ func (p FormatProvider) Close() error {
type formatStore struct {
underlyingStore storage.Store
documentProcessor DocumentProcessor
macCrypto MACCrypto

marshal marshalFunc
}

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

structuredDocumentID, err := GenerateEDVCompatibleID()
if err != nil {
return fmt.Errorf("failed to generate EDV compatible ID: %w", err)
}

structuredDoc := edv.StructuredDocument{
ID: k,
ID: structuredDocumentID,
Content: content,
}

encryptedDoc, err := s.documentProcessor.Encrypt(&structuredDoc)
indexKeyMAC, err := s.macCrypto.ComputeMAC(keyindexKey)
if err != nil {
return fmt.Errorf("failed to compute MAC for index name in put method: %w", err)
}

indexValueMAC, err := s.macCrypto.ComputeMAC(k)
if err != nil {
return fmt.Errorf("failed to compute MAC for index value in put method: %w", err)
}

indexedAttribute := edv.IndexedAttribute{
Name: indexKeyMAC,
Value: indexValueMAC,
Unique: true,
}

indexedAttributeCollection := edv.IndexedAttributeCollection{
HMAC: edv.IDTypePair{},
IndexedAttributes: []edv.IndexedAttribute{indexedAttribute},
}

indexedAttributeCollections := []edv.IndexedAttributeCollection{indexedAttributeCollection}

encryptedDoc, err := s.documentProcessor.Encrypt(&structuredDoc, indexedAttributeCollections)
if err != nil {
return fmt.Errorf(failEncryptStructuredDocument, err)
}
Expand All @@ -134,15 +184,45 @@ 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) {
indexKeyMAC, err := s.macCrypto.ComputeMAC(keyindexKey)
if err != nil {
return nil, fmt.Errorf(failGetFromUnderlyingStore, err)
return nil, fmt.Errorf("failed to compute MAC for index name: %w", err)
}

indexValueMAC, err := s.macCrypto.ComputeMAC(k)
if err != nil {
return nil, fmt.Errorf("failed to compute MAC for index value: %w", err)
}

matchingDocumentsIterator, err := s.underlyingStore.Query(indexKeyMAC, 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)
}
Expand All @@ -165,11 +245,11 @@ func (s formatStore) Get(k string) ([]byte, error) {
return payloadValueBytes, nil
}

func (s formatStore) Iterator(startKey, endKey string) storage.StoreIterator {
func (s *formatStore) Iterator(startKey, endKey string) storage.StoreIterator {
return s.underlyingStore.Iterator(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)
Expand All @@ -178,11 +258,31 @@ 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(indexKey string, indexValue string) (storage.StoreIterator, error) {
iterator, err := s.underlyingStore.Query(indexKey, indexValue)
if err != nil {
return nil, fmt.Errorf(failQueryUnderlyingStore, err)
}

return iterator, err
}

type generateRandomBytesFunc func([]byte) (int, error)

// GenerateEDVCompatibleID generates an EDV compatible ID using a cryptographically secure random number generator.
func GenerateEDVCompatibleID() (string, error) {
return generateEDVCompatibleID(rand.Read)
}

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
}
4 changes: 2 additions & 2 deletions pkg/storage/edv/formatprovider/formatprovider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,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)
})
Expand All @@ -357,7 +357,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)
})
Expand Down
2 changes: 2 additions & 0 deletions pkg/storage/edv/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
2 changes: 1 addition & 1 deletion pkg/storage/mem/mem_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ func (s *memStore) Delete(k string) error {
}

// TODO #2228 - implement query method.
func (s *memStore) Query(_ string) (storage.StoreIterator, error) {
func (s *memStore) Query(indexKey string, indexValue string) (storage.StoreIterator, error) {
return nil, storage.ErrQueryingNotSupported
}

Expand Down
5 changes: 2 additions & 3 deletions pkg/storage/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,8 @@ type Store interface {
// 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 values that satisfy the query.
Query(indexKey string, indexValue string) (StoreIterator, error)
}

// StoreIterator is the iterator for the latest snapshot of the underlying store.
Expand Down
2 changes: 1 addition & 1 deletion pkg/storage/wrapper/prefix/prefix_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(indexKey string, indexValue string) (storage.StoreIterator, error) {
return nil, storage.ErrQueryingNotSupported
}

0 comments on commit 57eeefe

Please sign in to comment.