package crypto_test import ( "bytes" "context" "encoding/json" "testing" "time" "github.com/go-kivik/kivik/v4" "github.com/go-kivik/kivik/v4/driver" kivikmock "github.com/go-kivik/kivik/v4/mockdb" "edge-infra.dev/pkg/edge/iam/crypto" "edge-infra.dev/pkg/edge/iam/storage/database" ) // this function tests RotateCouchEncryptionKey(), which given two keys (old & new), grabs whats in // the couchdb database, decrypts it with the old key, then re-encrypts it with the new key func TestCouchDBSwapFunction(t *testing.T) { oldKey := []byte("my-32bit-super-extra-secret-key!") newKey := []byte("my-32bit-super-extra-secret-key2") client, mock, err := kivikmock.New() if err != nil { panic(err) } // creating store with mock couchdb s, err := NewCouchStoreWithMock(client) if err != nil { t.Errorf("Unable create mock store with couchdb %s\n", err) return } // new mock db db := mock.NewDB() // returns a *driver.Document document, err := createCouchData(oldKey) if err != nil { t.Errorf("Unable to create couch document %s\n", err) return } // marshal to put into a Row docAsBytes, err := json.Marshal(document) if err != nil { t.Errorf("Unable to marshal document %s\n", err) return } docReader := bytes.NewReader(docAsBytes) // construct the Row w/ doc row := &driver.Row{ ID: "test-doc", Doc: docReader, } // adding the Row to the list of Rows rows := kivikmock.NewRows().AddRow(row) // what the mock couchdb expects to happen mock.ExpectDB().WillReturn(db) db.ExpectAllDocs().WillReturn(rows) mock.ExpectDB().WillReturn(db) db.ExpectGet().WillReturn(document) mock.ExpectDB().WillReturn(db) db.ExpectPut() // actually test the swapping of encryption keys against the document err = s.RotateCouchEncryptionKey(context.Background(), oldKey, newKey) if err != nil { t.Errorf("Unable to update couchdb data with new key %s\n", err) return } } // this tests the function GetDocWithKey, which retrieves a document with a given key // this is different than getDoc as it assumes 1. we're encrypted and 2. you have to provide a key func TestInnerUpdateFunction(t *testing.T) { value := []byte(`{"number": 1,"title": "test","url": "https://google.com"}`) key := []byte("my-32bit-super-extra-secret-key!") client, mock, err := kivikmock.New() if err != nil { panic(err) } s, err := NewCouchStoreWithMock(client) if err != nil { t.Errorf("Unable create mock store with couchdb %s\n", err) return } db := mock.NewDB() // returns a *driver.Document document, err := createCouchData(key) if err != nil { t.Errorf("Unable to create couch document %s\n", err) return } // what mock db expects will happen mock.ExpectDB().WillReturn(db) db.ExpectGet().WillReturn(document) // return the item from the db with a given key, unencrypted unencryptedDoc, err := s.GetDocWithKey(context.Background(), "test-doc", key) if err != nil { t.Errorf("Unable to get test data with new key %s\n", err) return } // confirming we did retrieve a doc if unencryptedDoc == nil { t.Errorf("Was not able to retrieve and decrypt doc %s\n", err) return } // confirming it is unencrypted if !bytes.Equal(unencryptedDoc.Value, value) { t.Errorf("Unable to get test data with new key %s\n", err) return } } // creates a sample couchdb Document func createCouchData(key []byte) (*driver.Document, error) { value := []byte(`{"number": 1,"title": "test","url": "https://google.com"}`) encrypted, err := crypto.EncryptJSON(value, key) if err != nil { return &driver.Document{}, err } doc := database.Doc{ ID: "test-doc", Value: encrypted, Expiration: time.Now().Add(time.Hour).Unix(), } // Convert the Docs struct into a Kivik driver.Document kivikDoc, err := kivikmock.Document(&doc) if err != nil { return &driver.Document{}, err } return kivikDoc, err } // new store with a mock couchDB client func NewCouchStoreWithMock(client *kivik.Client) (*database.Store, error) { store := &database.Store{ CouchDB: client, } return store, nil }