Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Encrypted Store Decorator #21

Merged
merged 4 commits into from
May 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,4 @@ https://verraes.net/2019/05/eventsourcing-patterns-throw-away-the-key/

* [Encrypt/Decrypt Event](../pkg/crypto/eventencryptor/encrypt_event_test.go)
* [Delete Encryption Key](../pkg/crypto/eventencryptor/delete_encryption_key_test.go)
* [Auto Encrypt/Decrypt Event with Decryption Store](../provider/encryptedstore/encrypt_event_test.go)
22 changes: 22 additions & 0 deletions pkg/crypto/cryptotest/failing_event_encryptor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package cryptotest

import (
"fmt"

"github.com/inklabs/rangedb"
)

type failingEventEncryptor struct{}

// NewFailingEventEncryptor always errors on Encrypt/Decrypt
func NewFailingEventEncryptor() *failingEventEncryptor {
return &failingEventEncryptor{}
}

func (f *failingEventEncryptor) Encrypt(_ rangedb.Event) error {
return fmt.Errorf("failingEventEncryptor:Encrypt")
}

func (f *failingEventEncryptor) Decrypt(_ rangedb.Event) error {
return fmt.Errorf("failingEventEncryptor:Decrypt")
}
50 changes: 50 additions & 0 deletions provider/encryptedstore/decrypting_record_iterator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package encryptedstore

import (
"github.com/inklabs/rangedb"
"github.com/inklabs/rangedb/pkg/crypto"
)

type decryptingRecordIterator struct {
parent rangedb.RecordIterator
eventEncryptor crypto.EventEncryptor
currentErr error
}

// NewDecryptingRecordIterator constructs a new rangedb.Record iterator that decrypts events
func NewDecryptingRecordIterator(parent rangedb.RecordIterator, eventEncryptor crypto.EventEncryptor) *decryptingRecordIterator {
return &decryptingRecordIterator{
parent: parent,
eventEncryptor: eventEncryptor,
}
}

func (i *decryptingRecordIterator) Next() bool {
if i.currentErr != nil {
return false
}

return i.parent.Next()
}

func (i *decryptingRecordIterator) Record() *rangedb.Record {
record := i.parent.Record()

if rangedbEvent, ok := record.Data.(rangedb.Event); ok {
err := i.eventEncryptor.Decrypt(rangedbEvent)
if err != nil {
i.currentErr = err
return nil
}
}

return record
}

func (i *decryptingRecordIterator) Err() error {
if i.currentErr != nil {
return i.currentErr
}

return i.parent.Err()
}
106 changes: 106 additions & 0 deletions provider/encryptedstore/decrypting_record_iterator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package encryptedstore_test

import (
"testing"

"github.com/inklabs/rangedb"
"github.com/inklabs/rangedb/pkg/crypto/aes"
"github.com/inklabs/rangedb/pkg/crypto/cryptotest"
"github.com/inklabs/rangedb/pkg/crypto/eventencryptor"
"github.com/inklabs/rangedb/pkg/crypto/provider/inmemorykeystore"
"github.com/inklabs/rangedb/provider/encryptedstore"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestNewDecryptingRecordIterator(t *testing.T) {
t.Run("decrypts one record: succeeds", func(t *testing.T) {
// Given
aesEncryptor := aes.NewGCM()
keyStore := inmemorykeystore.New()
eventEncryptor := eventencryptor.New(keyStore, aesEncryptor)
const email = "john@example.com"
encryptedEvent := &cryptotest.CustomerSignedUp{
ID: "fa14d796bab84c9f9c2026a5324d6a34",
Name: "John Doe",
Email: email,
Status: "active",
}
require.NoError(t, eventEncryptor.Encrypt(encryptedEvent))
record := &rangedb.Record{
AggregateType: encryptedEvent.AggregateType(),
AggregateID: encryptedEvent.AggregateID(),
GlobalSequenceNumber: 0,
StreamSequenceNumber: 0,
EventType: encryptedEvent.EventType(),
InsertTimestamp: 0,
Data: encryptedEvent,
Metadata: nil,
}

resultRecords := make(chan rangedb.ResultRecord, 1)
resultRecords <- rangedb.ResultRecord{
Record: record,
Err: nil,
}
close(resultRecords)
recordIterator := rangedb.NewRecordIterator(resultRecords)
decryptingIterator := encryptedstore.NewDecryptingRecordIterator(recordIterator, eventEncryptor)

// When
assert.True(t, decryptingIterator.Next())

// Then
require.NoError(t, decryptingIterator.Err())
actualRecord := decryptingIterator.Record()
assert.Equal(t, email, actualRecord.Data.(*cryptotest.CustomerSignedUp).Email)
assert.False(t, decryptingIterator.Next())
})

t.Run("decrypt two records: fails on first record", func(t *testing.T) {
// Given
failingEventEncryptor := cryptotest.NewFailingEventEncryptor()
const email = "john@example.com"
encryptedEvent := &cryptotest.CustomerSignedUp{
ID: "fa14d796bab84c9f9c2026a5324d6a34",
Name: "John Doe",
Email: email,
Status: "active",
}
aesEncryptor := aes.NewGCM()
keyStore := inmemorykeystore.New()
eventEncryptor := eventencryptor.New(keyStore, aesEncryptor)
require.NoError(t, eventEncryptor.Encrypt(encryptedEvent))
record := &rangedb.Record{
AggregateType: encryptedEvent.AggregateType(),
AggregateID: encryptedEvent.AggregateID(),
GlobalSequenceNumber: 0,
StreamSequenceNumber: 0,
EventType: encryptedEvent.EventType(),
InsertTimestamp: 0,
Data: encryptedEvent,
Metadata: nil,
}

resultRecords := make(chan rangedb.ResultRecord, 2)
resultRecords <- rangedb.ResultRecord{
Record: record,
Err: nil,
}
resultRecords <- rangedb.ResultRecord{
Record: record,
Err: nil,
}
close(resultRecords)
recordIterator := rangedb.NewRecordIterator(resultRecords)
decryptingIterator := encryptedstore.NewDecryptingRecordIterator(recordIterator, failingEventEncryptor)

// When
assert.True(t, decryptingIterator.Next())

// Then
assert.Nil(t, decryptingIterator.Record())
require.EqualError(t, decryptingIterator.Err(), "failingEventEncryptor:Decrypt")
assert.False(t, decryptingIterator.Next())
})
}
27 changes: 27 additions & 0 deletions provider/encryptedstore/decrypting_record_subscriber.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package encryptedstore

import (
"github.com/inklabs/rangedb"
"github.com/inklabs/rangedb/pkg/crypto"
)

type decryptingRecordSubscriber struct {
parent rangedb.RecordSubscriber
eventEncryptor crypto.EventEncryptor
}

// NewDecryptingRecordSubscriber decrypts records on Accept
func NewDecryptingRecordSubscriber(parent rangedb.RecordSubscriber, eventEncryptor crypto.EventEncryptor) *decryptingRecordSubscriber {
return &decryptingRecordSubscriber{
parent: parent,
eventEncryptor: eventEncryptor,
}
}

func (d *decryptingRecordSubscriber) Accept(record *rangedb.Record) {
if rangedbEvent, ok := record.Data.(rangedb.Event); ok {
_ = d.eventEncryptor.Decrypt(rangedbEvent)
}

d.parent.Accept(record)
}
94 changes: 94 additions & 0 deletions provider/encryptedstore/decrypting_record_subscriber_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package encryptedstore_test

import (
"testing"

"github.com/inklabs/rangedb"
"github.com/inklabs/rangedb/pkg/crypto/aes"
"github.com/inklabs/rangedb/pkg/crypto/cryptotest"
"github.com/inklabs/rangedb/pkg/crypto/eventencryptor"
"github.com/inklabs/rangedb/pkg/crypto/provider/inmemorykeystore"
"github.com/inklabs/rangedb/provider/encryptedstore"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestNewDecryptingRecordSubscriber(t *testing.T) {
t.Run("accept succeeds", func(t *testing.T) {
// Given
const email = "john@example.com"
encryptedEvent := &cryptotest.CustomerSignedUp{
ID: "fa14d796bab84c9f9c2026a5324d6a34",
Name: "John Doe",
Email: email,
Status: "active",
}
aesEncryptor := aes.NewGCM()
keyStore := inmemorykeystore.New()
eventEncryptor := eventencryptor.New(keyStore, aesEncryptor)
require.NoError(t, eventEncryptor.Encrypt(encryptedEvent))
encryptedRecord := &rangedb.Record{
AggregateType: encryptedEvent.AggregateType(),
AggregateID: encryptedEvent.AggregateID(),
GlobalSequenceNumber: 0,
StreamSequenceNumber: 0,
EventType: encryptedEvent.EventType(),
InsertTimestamp: 0,
Data: encryptedEvent,
Metadata: nil,
}
records := make(chan *rangedb.Record, 1)
defer close(records)
parent := rangedb.RecordSubscriberFunc(func(record *rangedb.Record) {
records <- record
})
subscriber := encryptedstore.NewDecryptingRecordSubscriber(parent, eventEncryptor)

// When
subscriber.Accept(encryptedRecord)

// Then
actualRecord := <-records
assert.Equal(t, email, actualRecord.Data.(*cryptotest.CustomerSignedUp).Email)
})

t.Run("accept ignores decryption errors", func(t *testing.T) {
// Given
const email = "john@example.com"
encryptedEvent := &cryptotest.CustomerSignedUp{
ID: "fa14d796bab84c9f9c2026a5324d6a34",
Name: "John Doe",
Email: email,
Status: "active",
}
aesEncryptor := aes.NewGCM()
keyStore := inmemorykeystore.New()
eventEncryptor := eventencryptor.New(keyStore, aesEncryptor)
require.NoError(t, eventEncryptor.Encrypt(encryptedEvent))

failingEventEncryptor := cryptotest.NewFailingEventEncryptor()
encryptedRecord := &rangedb.Record{
AggregateType: encryptedEvent.AggregateType(),
AggregateID: encryptedEvent.AggregateID(),
GlobalSequenceNumber: 0,
StreamSequenceNumber: 0,
EventType: encryptedEvent.EventType(),
InsertTimestamp: 0,
Data: encryptedEvent,
Metadata: nil,
}
records := make(chan *rangedb.Record, 1)
defer close(records)
parent := rangedb.RecordSubscriberFunc(func(record *rangedb.Record) {
records <- record
})
subscriber := encryptedstore.NewDecryptingRecordSubscriber(parent, failingEventEncryptor)

// When
subscriber.Accept(encryptedRecord)

// Then
actualRecord := <-records
assert.NotEqual(t, email, actualRecord.Data.(*cryptotest.CustomerSignedUp).Email)
})
}
70 changes: 70 additions & 0 deletions provider/encryptedstore/encrypt_event_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package encryptedstore_test

import (
"context"
"fmt"
"math/rand"

"github.com/inklabs/rangedb"
"github.com/inklabs/rangedb/pkg/crypto/aes"
"github.com/inklabs/rangedb/pkg/crypto/cryptotest"
"github.com/inklabs/rangedb/pkg/crypto/eventencryptor"
"github.com/inklabs/rangedb/pkg/crypto/provider/inmemorykeystore"
"github.com/inklabs/rangedb/pkg/shortuuid"
"github.com/inklabs/rangedb/provider/encryptedstore"
"github.com/inklabs/rangedb/provider/inmemorystore"
)

func ExampleNew_automatically_encrypt_decrypt() {
// Given
shortuuid.SetRand(100)
seededRandReader := rand.New(rand.NewSource(100))
aesEncryptor := aes.NewGCM()
aesEncryptor.SetRandReader(seededRandReader)
keyStore := inmemorykeystore.New()
eventEncryptor := eventencryptor.New(keyStore, aesEncryptor)
eventEncryptor.SetRandReader(seededRandReader)
event := &cryptotest.CustomerSignedUp{
ID: "fe65ac8d8c3a45e9b3cee407f10ee518",
Name: "John Doe",
Email: "john@example.com",
Status: "active",
}
inMemoryStore := inmemorystore.New()
encryptedStore := encryptedstore.New(inMemoryStore, eventEncryptor)
encryptedStore.Bind(&cryptotest.CustomerSignedUp{})

ctx := context.Background()

// When
_, err := encryptedStore.Save(ctx, &rangedb.EventRecord{Event: event})
PrintError(err)

fmt.Println("Auto Decrypted Event:")
recordIterator := encryptedStore.Events(ctx, 0)
for recordIterator.Next() {
PrintEvent(recordIterator.Record().Data.(rangedb.Event))
}

fmt.Println("Raw Encrypted Event:")
rawRecordIterator := inMemoryStore.Events(ctx, 0)
for rawRecordIterator.Next() {
PrintEvent(rawRecordIterator.Record().Data.(rangedb.Event))
}

// Output:
// Auto Decrypted Event:
// {
// "ID": "fe65ac8d8c3a45e9b3cee407f10ee518",
// "Name": "John Doe",
// "Email": "john@example.com",
// "Status": "active"
// }
// Raw Encrypted Event:
// {
// "ID": "fe65ac8d8c3a45e9b3cee407f10ee518",
// "Name": "Lp5pGK8QGYw3NJyJVBsW49HESSf+NEraAQoBmpLXboZvsN/L",
// "Email": "o1H9t1BClYc5UcyUV+Roe3wz5gwRZRjgBI/xzwZs8ueQGQ5L8uGnbrTGrh8=",
// "Status": "active"
// }
}
Loading