Alex Jbanca e8c4b7647f
chore(upgradeSQLCipher): Upgrading SQLCipher to version 5.4.5 (#3559)
* chore(upgradeSQLCipher): Upgrading SQLCipher to version 5.4.5

Changes:
### github.com/mutecomm/go-sqlcipher
1. The improved crypto argorighms from go-sqlcipher v3 are merged in v4
	Tags:
	v4.4.2-status.1 - merge `burn_stack` improvement
	v4.4.2-status.2 - merge `SHA1` improvement
	v4.4.2-status.4- merge 'AES' improvement
2. Fixed `go-sqlcipher` to support v3 database in compatibility mode (`sqlcipher` already supports this) (Tag: v4.4.2-status.3)
3. Upgrade `sqlcipher` to v5.4.5 (Tag: v4.5.4-status.1)

### github.com/status-im/migrate/v4
1. Upgrade `go-sqlcipher` version in `github.com/status-im/migrate/v4`

### status-go
1. Upgrade `go-sqlcipher` and `migrate` modules in status-go
2. Configure the DB connections to open the DB in v3 compatibility mode

* chore(upgradeSQLCipher): Use sqlcipher v3 configuration to encrypt a plain text database

* chore(upgradeSQLCipher): Scanning NULL BLOB value should return nil

Fixing failing tests: TestSyncDeviceSuite/TestPairingSyncDeviceClientAsReceiver;  TestSyncDeviceSuite/TestPairingSyncDeviceClientAsSender

Considering the following configuration:
1. Table with BLOB column has 1 NULL value
2. Query the value
3. Rows.Scan(&dest sql.NullString)

Expected: dest.Valid == false; dest.String == nil
Actual: dest.Valid == true; dest.String == ""

* chore: Bump go-sqlcipher version to include NULL BLOB fix
2023-06-07 08:58:01 +03:00

170 lines
4.5 KiB
Go

package sqlite
import (
"database/sql"
"fmt"
"os"
"github.com/pkg/errors"
_ "github.com/mutecomm/go-sqlcipher/v4" // We require go sqlcipher that overrides default implementation
"github.com/status-im/migrate/v4"
"github.com/status-im/migrate/v4/database/sqlcipher"
bindata "github.com/status-im/migrate/v4/source/go_bindata"
mvdsmigrations "github.com/vacp2p/mvds/persistenceutil"
)
// The reduced number of kdf iterations (for performance reasons) which is
// currently used for derivation of the database key
// https://github.com/status-im/status-go/pull/1343
// https://notes.status.im/i8Y_l7ccTiOYq09HVgoFwA
const ReducedKDFIterationsNumber = 3200
const InMemoryPath = ":memory:"
var migrationsTable = "status_protocol_go_" + sqlcipher.DefaultMigrationsTable
// MigrationConfig is a struct that allows to define bindata migrations.
type MigrationConfig struct {
AssetNames []string
AssetGetter func(name string) ([]byte, error)
}
// Open opens or initializes a new database for a given file path.
// MigrationConfig is optional but if provided migrations are applied automatically.
func Open(path, key string, kdfIterationNumber int) (*sql.DB, error) {
return openAndMigrate(path, key, kdfIterationNumber)
}
// OpenInMemory opens an in memory SQLite database.
// Number of KDF iterations is reduced to 0.
func OpenInMemory() (*sql.DB, error) {
return openAndMigrate(InMemoryPath, "", 0)
}
// OpenWithIter allows to open a new database with a custom number of kdf iterations.
// Higher kdf iterations number makes it slower to open the database.
func OpenWithIter(path, key string, kdfIter int) (*sql.DB, error) {
return openAndMigrate(path, key, kdfIter)
}
func open(path string, key string, kdfIter int) (*sql.DB, error) {
if path != InMemoryPath {
_, err := os.OpenFile(path, os.O_CREATE, 0600)
if err != nil {
return nil, err
}
}
db, err := sql.Open("sqlite3", path)
if err != nil {
return nil, err
}
keyString := fmt.Sprintf("PRAGMA key = '%s'", key)
// Disable concurrent access as not supported by the driver
db.SetMaxOpenConns(1)
if _, err = db.Exec("PRAGMA foreign_keys=ON"); err != nil {
return nil, err
}
if _, err = db.Exec(keyString); err != nil {
return nil, err
}
kdfString := fmt.Sprintf("PRAGMA kdf_iter = '%d'", kdfIter)
if _, err = db.Exec(kdfString); err != nil {
return nil, err
}
return db, nil
}
func openAndMigrate(path string, key string, kdfIter int) (*sql.DB, error) {
db, err := open(path, key, kdfIter)
if err != nil {
return nil, err
}
if err := Migrate(db); err != nil {
return nil, err
}
return db, nil
}
// applyMigrations allows to apply bindata migrations on the current *sql.DB.
// `assetNames` is a list of assets with migrations and `assetGetter` is responsible
// for returning the content of the asset with a given name.
func applyMigrations(db *sql.DB, assetNames []string, assetGetter func(name string) ([]byte, error)) error {
resources := bindata.Resource(
assetNames,
assetGetter,
)
source, err := bindata.WithInstance(resources)
if err != nil {
return errors.Wrap(err, "failed to create migration source")
}
driver, err := sqlcipher.WithInstance(db, &sqlcipher.Config{
MigrationsTable: migrationsTable,
})
if err != nil {
return errors.Wrap(err, "failed to create driver")
}
m, err := migrate.NewWithInstance(
"go-bindata",
source,
"sqlcipher",
driver,
)
if err != nil {
return errors.Wrap(err, "failed to create migration instance")
}
version, dirty, err := m.Version()
if err != nil && err != migrate.ErrNilVersion {
return errors.Wrap(err, "could not get version")
}
err = ApplyAdHocMigrations(version, dirty, m, db)
if err != nil {
return errors.Wrap(err, "failed to apply ad-hoc migrations")
}
if dirty {
err = ReplayLastMigration(version, m)
if err != nil {
return errors.Wrap(err, "failed to replay last migration")
}
}
if err = m.Up(); err != migrate.ErrNoChange {
return errors.Wrap(err, "failed to migrate")
}
return nil
}
func Migrate(database *sql.DB) error {
// Apply migrations for all components.
err := mvdsmigrations.Migrate(database)
if err != nil {
return errors.Wrap(err, "failed to apply mvds migrations")
}
migrationNames, migrationGetter, err := prepareMigrations(defaultMigrations)
if err != nil {
return errors.Wrap(err, "failed to prepare status-go/protocol migrations")
}
err = applyMigrations(database, migrationNames, migrationGetter)
if err != nil {
return errors.Wrap(err, "failed to apply status-go/protocol migrations")
}
return nil
}