Skip to content

Commit

Permalink
Adding Postgre KeyStore impl
Browse files Browse the repository at this point in the history
  • Loading branch information
pdt256 committed Jan 23, 2021
1 parent 75fe361 commit 836e24b
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 8 deletions.
3 changes: 3 additions & 0 deletions pkg/crypto/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,8 @@ var ErrKeyNotFound = fmt.Errorf("key not found")
// ErrKeyExistsForSubjectID encryption key has already been set for subjectID.
var ErrKeyExistsForSubjectID = fmt.Errorf("key already exists for subjectID")

// ErrKeyAlreadyUsed encryption key has already been set for subjectID.
var ErrKeyAlreadyUsed = fmt.Errorf("encryption key already used")

// ErrInvalidKey encryption key is not valid.
var ErrInvalidKey = fmt.Errorf("invalid encryption key")
17 changes: 17 additions & 0 deletions pkg/crypto/cryptotest/verify_key_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,5 +95,22 @@ func VerifyKeyStore(t *testing.T, newStore func(t *testing.T) crypto.KeyStore) {
// Then
require.Equal(t, crypto.ErrInvalidKey, err)
})

t.Run("errors due to duplicate encryption key for two subjectIDs", func(t *testing.T) {
t.Skip("TODO: add support for unique secrets")
// Given
const key = "062cb6d874ac49f4ac48e3ff7b0124d3"
subjectID1 := shortuuid.New().String()
subjectID2 := shortuuid.New().String()
store := newStore(t)
err := store.Set(subjectID1, key)
require.NoError(t, err)

// When
err = store.Set(subjectID2, key)

// Then
require.Equal(t, crypto.ErrKeyAlreadyUsed, err)
})
})
}
21 changes: 14 additions & 7 deletions pkg/crypto/provider/inmemorykeystore/inmemory_keystore.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,23 @@ import (
)

type inMemoryKeyStore struct {
mux sync.RWMutex
EncryptionKeys map[string]string
mux sync.RWMutex
EncryptionKeysBySubjectID map[string]string
EncryptionKeys map[string]struct{}
}

func New() *inMemoryKeyStore {
return &inMemoryKeyStore{
EncryptionKeys: make(map[string]string),
EncryptionKeysBySubjectID: make(map[string]string),
EncryptionKeys: make(map[string]struct{}),
}
}

func (i *inMemoryKeyStore) Get(subjectID string) (string, error) {
i.mux.RLock()
defer i.mux.RUnlock()

if key, ok := i.EncryptionKeys[subjectID]; ok {
if key, ok := i.EncryptionKeysBySubjectID[subjectID]; ok {
if key == "" {
return "", crypto.ErrKeyWasDeleted
}
Expand All @@ -40,11 +42,16 @@ func (i *inMemoryKeyStore) Set(subjectID, key string) error {
i.mux.Lock()
defer i.mux.Unlock()

if _, ok := i.EncryptionKeys[subjectID]; ok {
if _, ok := i.EncryptionKeys[key]; ok {
return crypto.ErrKeyAlreadyUsed
}

if _, ok := i.EncryptionKeysBySubjectID[subjectID]; ok {
return crypto.ErrKeyExistsForSubjectID
}

i.EncryptionKeys[subjectID] = key
i.EncryptionKeysBySubjectID[subjectID] = key
i.EncryptionKeys[key] = struct{}{}

return nil
}
Expand All @@ -53,7 +60,7 @@ func (i *inMemoryKeyStore) Delete(subjectID string) error {
i.mux.Lock()
defer i.mux.Unlock()

i.EncryptionKeys[subjectID] = ""
i.EncryptionKeysBySubjectID[subjectID] = ""

return nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"github.com/inklabs/rangedb/pkg/crypto/provider/inmemorykeystore"
)

func TestInMemoryCrypto_VerifyEngineInterface(t *testing.T) {
func TestInMemoryCrypto_VerifyKeyStoreInterface(t *testing.T) {
cryptotest.VerifyKeyStore(t, func(t *testing.T) crypto.KeyStore {
return inmemorykeystore.New()
})
Expand Down
133 changes: 133 additions & 0 deletions pkg/crypto/provider/postgreskeystore/postgres_keystore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package postgreskeystore

import (
"database/sql"
"fmt"
"time"

"github.com/lib/pq"

"github.com/inklabs/rangedb/pkg/crypto"
"github.com/inklabs/rangedb/provider/postgresstore"
)

const (
PgUniqueViolationCode = pq.ErrorCode("23505")
PgDuplicateSubjectIDViolation = "vault_pkey"
PgDuplicateEncryptionKeyViolation = "vault_encryptionkey_key"
)

type postgresKeyStore struct {
config *postgresstore.Config
db *sql.DB
}

func New(config *postgresstore.Config) (*postgresKeyStore, error) {
p := &postgresKeyStore{
config: config,
}

err := p.connectToDB()
if err != nil {
return nil, err
}
err = p.initDB()
if err != nil {
return nil, err
}

return p, nil
}

func (p *postgresKeyStore) Get(subjectID string) (string, error) {
row := p.db.QueryRow("SELECT EncryptionKey, DeletedAtTimestamp FROM vault WHERE SubjectID = $1",
subjectID)

var encryptionKey string
var deletedAtTimestamp *uint64
err := row.Scan(&encryptionKey, &deletedAtTimestamp)
if err != nil {
if err == sql.ErrNoRows {
return "", crypto.ErrKeyNotFound
}

return "", err
}

if deletedAtTimestamp != nil {
return "", crypto.ErrKeyWasDeleted
}

return encryptionKey, nil
}

func (p *postgresKeyStore) Set(subjectID, encryptionKey string) error {
if encryptionKey == "" {
return crypto.ErrInvalidKey
}

_, err := p.db.Exec("INSERT INTO vault (SubjectID, EncryptionKey) VALUES ($1, $2)",
subjectID, encryptionKey)
if err != nil {
if err, ok := err.(*pq.Error); ok {
if err.Code == PgUniqueViolationCode {
switch err.Constraint {
case PgDuplicateSubjectIDViolation:
return crypto.ErrKeyExistsForSubjectID

case PgDuplicateEncryptionKeyViolation:
return crypto.ErrKeyAlreadyUsed
}
}
}
return err
}

return nil
}

func (p *postgresKeyStore) Delete(subjectID string) error {
_, err := p.db.Exec("UPDATE vault SET DeletedAtTimestamp = $1 WHERE SubjectID = $2",
time.Now().Unix(),
subjectID)
if err != nil {
return err
}

return nil
}

func (p *postgresKeyStore) connectToDB() error {
db, err := sql.Open("postgres", p.config.DataSourceName())
if err != nil {
return fmt.Errorf("unable to open DB connection: %v", err)
}

err = db.Ping()
if err != nil {
return fmt.Errorf("unable to connect to DB: %v", err)
}

p.db = db

return nil
}

func (p *postgresKeyStore) initDB() error {
sqlStatements := []string{
`CREATE TABLE IF NOT EXISTS vault (
SubjectID TEXT PRIMARY KEY,
EncryptionKey TEXT UNIQUE,
DeletedAtTimestamp BIGINT
);`,
}

for _, statement := range sqlStatements {
_, err := p.db.Exec(statement)
if err != nil {
return err
}
}

return nil
}
37 changes: 37 additions & 0 deletions pkg/crypto/provider/postgreskeystore/postgres_keystore_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package postgreskeystore_test

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/inklabs/rangedb/pkg/crypto"
"github.com/inklabs/rangedb/pkg/crypto/cryptotest"
"github.com/inklabs/rangedb/pkg/crypto/provider/postgreskeystore"
"github.com/inklabs/rangedb/provider/postgresstore"
)

func TestPostgresKeyStore_VerifyKeyStoreInterface(t *testing.T) {
config := configFromEnvironment(t)

cryptotest.VerifyKeyStore(t, func(t *testing.T) crypto.KeyStore {
keyStore, err := postgreskeystore.New(config)
require.NoError(t, err)

return keyStore
})
}

type testSkipper interface {
Skip(args ...interface{})
}

// TODO: Move postgresstore.Config to separate package
func configFromEnvironment(t testSkipper) *postgresstore.Config {
config, err := postgresstore.NewConfigFromEnvironment()
if err != nil {
t.Skip("Postgres DB has not been configured via environment variables to run integration tests")
}

return config
}

0 comments on commit 836e24b

Please sign in to comment.