8
vendor/github.com/status-im/status-go/sqlite/driver.go
generated
vendored
Normal file
8
vendor/github.com/status-im/status-go/sqlite/driver.go
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
package sqlite
|
||||
|
||||
import "database/sql"
|
||||
|
||||
// statementCreator allows to pass transaction or database to use in consumer.
|
||||
type StatementCreator interface {
|
||||
Prepare(query string) (*sql.Stmt, error)
|
||||
}
|
||||
94
vendor/github.com/status-im/status-go/sqlite/fields.go
generated
vendored
Normal file
94
vendor/github.com/status-im/status-go/sqlite/fields.go
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// JSONBlob type for marshaling/unmarshaling inner type to json.
|
||||
type JSONBlob struct {
|
||||
Data interface{}
|
||||
Valid bool
|
||||
}
|
||||
|
||||
// Scan implements interface.
|
||||
func (blob *JSONBlob) Scan(value interface{}) error {
|
||||
dataVal := reflect.ValueOf(blob.Data)
|
||||
blob.Valid = false
|
||||
if value == nil || dataVal.Kind() == reflect.Ptr && dataVal.IsNil() {
|
||||
return nil
|
||||
}
|
||||
|
||||
var bytes []byte
|
||||
ok := true
|
||||
switch v := value.(type) {
|
||||
case []byte:
|
||||
bytes, ok = value.([]byte)
|
||||
case string:
|
||||
bytes = []byte(v)
|
||||
default:
|
||||
ok = false
|
||||
}
|
||||
if !ok {
|
||||
return errors.New("not a byte slice or string")
|
||||
}
|
||||
if len(bytes) == 0 {
|
||||
return nil
|
||||
}
|
||||
err := json.Unmarshal(bytes, blob.Data)
|
||||
blob.Valid = err == nil
|
||||
return err
|
||||
}
|
||||
|
||||
// Value implements interface.
|
||||
func (blob *JSONBlob) Value() (driver.Value, error) {
|
||||
dataVal := reflect.ValueOf(blob.Data)
|
||||
if (blob.Data == nil) || (dataVal.Kind() == reflect.Ptr && dataVal.IsNil()) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
switch dataVal.Kind() {
|
||||
case reflect.Slice, reflect.Array, reflect.Map:
|
||||
if dataVal.Len() == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
return json.Marshal(blob.Data)
|
||||
}
|
||||
|
||||
func BigIntToClampedInt64(val *big.Int) *int64 {
|
||||
if val == nil {
|
||||
return nil
|
||||
}
|
||||
var v int64
|
||||
if val.IsInt64() {
|
||||
v = val.Int64()
|
||||
} else {
|
||||
v = math.MaxInt64
|
||||
}
|
||||
return &v
|
||||
}
|
||||
|
||||
// BigIntToPadded128BitsStr converts a big.Int to a string, padding it with 0 to account for 128 bits size
|
||||
// Returns nil if input val is nil
|
||||
// This should work to sort and compare big.Ints values in SQLite
|
||||
func BigIntToPadded128BitsStr(val *big.Int) *string {
|
||||
if val == nil {
|
||||
return nil
|
||||
}
|
||||
hexStr := val.Text(16)
|
||||
res := new(string)
|
||||
*res = fmt.Sprintf("%032s", hexStr)
|
||||
return res
|
||||
}
|
||||
|
||||
func Int64ToPadded128BitsStr(val int64) *string {
|
||||
res := fmt.Sprintf("%032x", val)
|
||||
return &res
|
||||
}
|
||||
183
vendor/github.com/status-im/status-go/sqlite/migrate.go
generated
vendored
Normal file
183
vendor/github.com/status-im/status-go/sqlite/migrate.go
generated
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/status-im/migrate/v4"
|
||||
"github.com/status-im/migrate/v4/database/sqlcipher"
|
||||
bindata "github.com/status-im/migrate/v4/source/go_bindata"
|
||||
)
|
||||
|
||||
type CustomMigrationFunc func(tx *sql.Tx) error
|
||||
|
||||
type PostStep struct {
|
||||
Version uint
|
||||
CustomMigration CustomMigrationFunc
|
||||
RollBackVersion uint
|
||||
}
|
||||
|
||||
var migrationTable = "status_go_" + sqlcipher.DefaultMigrationsTable
|
||||
|
||||
// Migrate database with option to augment the migration steps with additional processing using the customSteps
|
||||
// parameter. For each PostStep entry in customSteps the CustomMigration will be called after the migration step
|
||||
// with the matching Version number has been executed. If the CustomMigration returns an error, the migration process
|
||||
// is aborted. In case the custom step failures the migrations are run down to RollBackVersion if > 0.
|
||||
//
|
||||
// The recommended way to create a custom migration is by providing empty and versioned run/down sql files as markers.
|
||||
// Then running all the SQL code inside the same transaction to transform and commit provides the possibility
|
||||
// to completely rollback the migration in case of failure, avoiding to leave the DB in an inconsistent state.
|
||||
//
|
||||
// Marker migrations can be created by using PostStep structs with specific Version numbers and a callback function,
|
||||
// even when no accompanying SQL migration is needed. This can be used to trigger Go code at specific points
|
||||
// during the migration process.
|
||||
//
|
||||
// Caution: This mechanism should be used as a last resort. Prefer data migration using SQL migration files
|
||||
// whenever possible to ensure consistency and compatibility with standard migration tools.
|
||||
//
|
||||
// untilVersion, for testing purposes optional parameter, can be used to limit the migration to a specific version.
|
||||
// Pass nil to migrate to the latest available version.
|
||||
func Migrate(db *sql.DB, resources *bindata.AssetSource, customSteps []*PostStep, untilVersion *uint) error {
|
||||
source, err := bindata.WithInstance(resources)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create bindata migration source: %w", err)
|
||||
}
|
||||
|
||||
driver, err := sqlcipher.WithInstance(db, &sqlcipher.Config{
|
||||
MigrationsTable: migrationTable,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create sqlcipher driver: %w", err)
|
||||
}
|
||||
|
||||
m, err := migrate.NewWithInstance("go-bindata", source, "sqlcipher", driver)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create migration instance: %w", err)
|
||||
}
|
||||
|
||||
if len(customSteps) == 0 {
|
||||
return runRemainingMigrations(m, untilVersion)
|
||||
}
|
||||
|
||||
sort.Slice(customSteps, func(i, j int) bool {
|
||||
return customSteps[i].Version < customSteps[j].Version
|
||||
})
|
||||
|
||||
lastVersion, err := getCurrentVersion(m, db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
customIndex := 0
|
||||
// ignore processed versions
|
||||
for customIndex < len(customSteps) && customSteps[customIndex].Version <= lastVersion {
|
||||
customIndex++
|
||||
}
|
||||
|
||||
if err := runCustomMigrations(m, db, customSteps, customIndex, untilVersion); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return runRemainingMigrations(m, untilVersion)
|
||||
}
|
||||
|
||||
// runCustomMigrations performs source migrations from current to each custom steps, then runs custom migration callback
|
||||
// until it executes all custom migrations or an error occurs and it tries to rollback to RollBackVersion if > 0.
|
||||
func runCustomMigrations(m *migrate.Migrate, db *sql.DB, customSteps []*PostStep, customIndex int, untilVersion *uint) error {
|
||||
for customIndex < len(customSteps) && (untilVersion == nil || customSteps[customIndex].Version <= *untilVersion) {
|
||||
customStep := customSteps[customIndex]
|
||||
|
||||
if err := m.Migrate(customStep.Version); err != nil && err != migrate.ErrNoChange {
|
||||
return fmt.Errorf("failed to migrate to version %d: %w", customStep.Version, err)
|
||||
}
|
||||
|
||||
if err := runCustomMigrationStep(db, customStep, m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
customIndex++
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runCustomMigrationStep(db *sql.DB, customStep *PostStep, m *migrate.Migrate) error {
|
||||
|
||||
sqlTx, err := db.Begin()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to begin transaction: %w", err)
|
||||
}
|
||||
|
||||
if err := customStep.CustomMigration(sqlTx); err != nil {
|
||||
_ = sqlTx.Rollback()
|
||||
return rollbackCustomMigration(m, customStep, err)
|
||||
}
|
||||
|
||||
if err := sqlTx.Commit(); err != nil {
|
||||
return fmt.Errorf("failed to commit transaction: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func rollbackCustomMigration(m *migrate.Migrate, customStep *PostStep, customErr error) error {
|
||||
if customStep.RollBackVersion > 0 {
|
||||
err := m.Migrate(customStep.RollBackVersion)
|
||||
newV, _, _ := m.Version()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to rollback migration to version %d: %w", customStep.RollBackVersion, err)
|
||||
}
|
||||
return fmt.Errorf("custom migration step failed for version %d. Successfully rolled back migration to version %d: %w", customStep.Version, newV, customErr)
|
||||
}
|
||||
return fmt.Errorf("custom migration step failed for version %d: %w", customStep.Version, customErr)
|
||||
}
|
||||
|
||||
func runRemainingMigrations(m *migrate.Migrate, untilVersion *uint) error {
|
||||
if untilVersion != nil {
|
||||
if err := m.Migrate(*untilVersion); err != nil && err != migrate.ErrNoChange {
|
||||
return fmt.Errorf("failed to migrate to version %d: %w", *untilVersion, err)
|
||||
}
|
||||
} else {
|
||||
if err := m.Up(); err != nil && err != migrate.ErrNoChange {
|
||||
ver, _, _ := m.Version()
|
||||
return fmt.Errorf("failed to migrate up: %w, current version: %d", err, ver)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getCurrentVersion(m *migrate.Migrate, db *sql.DB) (uint, error) {
|
||||
lastVersion, dirty, err := m.Version()
|
||||
if err != nil && err != migrate.ErrNilVersion {
|
||||
return 0, fmt.Errorf("failed to get migration version: %w", err)
|
||||
}
|
||||
if dirty {
|
||||
return 0, fmt.Errorf("DB is dirty after migration version %d", lastVersion)
|
||||
}
|
||||
if err == migrate.ErrNilVersion {
|
||||
lastVersion, _, err = GetLastMigrationVersion(db)
|
||||
return lastVersion, err
|
||||
}
|
||||
return lastVersion, nil
|
||||
}
|
||||
|
||||
// GetLastMigrationVersion returns the last migration version stored in the migration table.
|
||||
// Returns 0 for version in case migrationTableExists is true
|
||||
func GetLastMigrationVersion(db *sql.DB) (version uint, migrationTableExists bool, err error) {
|
||||
// Check if the migration table exists
|
||||
row := db.QueryRow("SELECT exists(SELECT name FROM sqlite_master WHERE type='table' AND name=?)", migrationTable)
|
||||
migrationTableExists = false
|
||||
err = row.Scan(&migrationTableExists)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return 0, false, err
|
||||
}
|
||||
|
||||
var lastMigration uint64 = 0
|
||||
if migrationTableExists {
|
||||
row = db.QueryRow("SELECT version FROM status_go_schema_migrations")
|
||||
err = row.Scan(&lastMigration)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return 0, true, err
|
||||
}
|
||||
}
|
||||
return uint(lastMigration), migrationTableExists, nil
|
||||
}
|
||||
323
vendor/github.com/status-im/status-go/sqlite/sqlite.go
generated
vendored
Normal file
323
vendor/github.com/status-im/status-go/sqlite/sqlite.go
generated
vendored
Normal file
@@ -0,0 +1,323 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
sqlcipher "github.com/mutecomm/go-sqlcipher/v4" // We require go sqlcipher that overrides default implementation
|
||||
|
||||
"github.com/status-im/status-go/common/dbsetup"
|
||||
)
|
||||
|
||||
const (
|
||||
// The reduced number of kdf iterations (for performance reasons) which is
|
||||
// used as the default value
|
||||
// https://github.com/status-im/status-go/pull/1343
|
||||
// https://notes.status.im/i8Y_l7ccTiOYq09HVgoFwA
|
||||
ReducedKDFIterationsNumber = 3200
|
||||
|
||||
// WALMode for sqlite.
|
||||
WALMode = "wal"
|
||||
InMemoryPath = ":memory:"
|
||||
V4CipherPageSize = 8192
|
||||
V3CipherPageSize = 1024
|
||||
sqlMainDatabase = "main"
|
||||
)
|
||||
|
||||
// DecryptDB completely removes the encryption from the db
|
||||
func DecryptDB(oldPath string, newPath string, key string, kdfIterationsNumber int) error {
|
||||
|
||||
db, err := openDB(oldPath, key, kdfIterationsNumber, V4CipherPageSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = db.Exec(`ATTACH DATABASE '` + newPath + `' AS plaintext KEY ''`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = db.Exec(`SELECT sqlcipher_export('plaintext')`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = db.Exec(`DETACH DATABASE plaintext`)
|
||||
return err
|
||||
}
|
||||
|
||||
func encryptDB(db *sql.DB, encryptedPath string, key string, kdfIterationsNumber int, onStart func(), onEnd func()) error {
|
||||
if onStart != nil {
|
||||
onStart()
|
||||
}
|
||||
if onEnd != nil {
|
||||
defer onEnd()
|
||||
}
|
||||
|
||||
attachedDbName := "encrypted"
|
||||
err := attachDatabaseWithDefaultSettings(db, encryptedPath, attachedDbName, key, kdfIterationsNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = db.Exec(fmt.Sprintf(`SELECT sqlcipher_export('%s')`, attachedDbName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = db.Exec(fmt.Sprintf(`DETACH DATABASE %s`, attachedDbName))
|
||||
return err
|
||||
}
|
||||
|
||||
func attachDatabaseWithDefaultSettings(db *sql.DB, attachedDbPath string, attachedDbName string, key string, kdfIterationsNumber int) error {
|
||||
_, err := db.Exec(fmt.Sprintf(`ATTACH DATABASE '%s' AS %s KEY '%s'`, attachedDbPath, attachedDbName, key))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if kdfIterationsNumber <= 0 {
|
||||
kdfIterationsNumber = dbsetup.ReducedKDFIterationsNumber
|
||||
}
|
||||
|
||||
if _, err := db.Exec(fmt.Sprintf(`PRAGMA %s.busy_timeout = 60000`, attachedDbName)); err != nil {
|
||||
return errors.New("failed to set `busy_timeout` pragma on attached db")
|
||||
}
|
||||
|
||||
return setDatabaseCipherSettings(db, kdfIterationsNumber, attachedDbName)
|
||||
}
|
||||
|
||||
func setDatabaseCipherSettings(db *sql.DB, kdfIterationsNumber int, dbNameOpt ...string) error {
|
||||
dbName := sqlMainDatabase
|
||||
if len(dbNameOpt) > 0 {
|
||||
dbName = dbNameOpt[0]
|
||||
}
|
||||
|
||||
_, err := db.Exec(fmt.Sprintf("PRAGMA %s.kdf_iter = '%d'", dbName, kdfIterationsNumber))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := db.Exec(fmt.Sprintf("PRAGMA %s.cipher_page_size = %d", dbName, V4CipherPageSize)); err != nil {
|
||||
fmt.Println("failed to set cipher_page_size pragma")
|
||||
return err
|
||||
}
|
||||
if _, err := db.Exec(fmt.Sprintf("PRAGMA %s.cipher_hmac_algorithm = HMAC_SHA1", dbName)); err != nil {
|
||||
fmt.Println("failed to set cipher_hmac_algorithm pragma")
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := db.Exec(fmt.Sprintf("PRAGMA %s.cipher_kdf_algorithm = PBKDF2_HMAC_SHA1", dbName)); err != nil {
|
||||
fmt.Println("failed to set cipher_kdf_algorithm pragma")
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncryptDB takes a plaintext database and adds encryption
|
||||
func EncryptDB(unencryptedPath string, encryptedPath string, key string, kdfIterationsNumber int, onStart func(), onEnd func()) error {
|
||||
_ = os.Remove(encryptedPath)
|
||||
|
||||
db, err := OpenUnecryptedDB(unencryptedPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return encryptDB(db, encryptedPath, key, kdfIterationsNumber, onStart, onEnd)
|
||||
}
|
||||
|
||||
// Export takes an encrypted database and re-encrypts it in a new file, with a new key
|
||||
func ExportDB(encryptedPath string, key string, kdfIterationsNumber int, newPath string, newKey string, onStart func(), onEnd func()) error {
|
||||
db, err := openDB(encryptedPath, key, kdfIterationsNumber, V4CipherPageSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
return encryptDB(db, newPath, newKey, kdfIterationsNumber, onStart, onEnd)
|
||||
}
|
||||
|
||||
func buildSqlcipherDSN(path string) (string, error) {
|
||||
if path == InMemoryPath {
|
||||
return InMemoryPath, nil
|
||||
}
|
||||
|
||||
// Adding sqlcipher query parameter to the DSN
|
||||
queryOperator := "?"
|
||||
|
||||
if queryStart := strings.IndexRune(path, '?'); queryStart != -1 {
|
||||
params, err := url.ParseQuery(path[queryStart+1:])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(params) > 0 {
|
||||
queryOperator = "&"
|
||||
}
|
||||
}
|
||||
|
||||
// We need to set txlock=immediate to avoid "database is locked" errors during concurrent write operations
|
||||
// This could happen when a read transaction is promoted to write transaction
|
||||
// https://www.sqlite.org/lang_transaction.html
|
||||
return path + queryOperator + "_txlock=immediate", nil
|
||||
}
|
||||
|
||||
func openDB(path string, key string, kdfIterationsNumber int, cipherPageSize int) (*sql.DB, error) {
|
||||
driverName := fmt.Sprintf("sqlcipher_with_extensions-%d", len(sql.Drivers()))
|
||||
sql.Register(driverName, &sqlcipher.SQLiteDriver{
|
||||
ConnectHook: func(conn *sqlcipher.SQLiteConn) error {
|
||||
if _, err := conn.Exec("PRAGMA foreign_keys=ON", []driver.Value{}); err != nil {
|
||||
return errors.New("failed to set `foreign_keys` pragma")
|
||||
}
|
||||
|
||||
if _, err := conn.Exec(fmt.Sprintf("PRAGMA key = '%s'", key), []driver.Value{}); err != nil {
|
||||
return errors.New("failed to set `key` pragma")
|
||||
}
|
||||
|
||||
if kdfIterationsNumber <= 0 {
|
||||
kdfIterationsNumber = dbsetup.ReducedKDFIterationsNumber
|
||||
}
|
||||
|
||||
if _, err := conn.Exec(fmt.Sprintf("PRAGMA cipher_page_size = %d", cipherPageSize), nil); err != nil {
|
||||
fmt.Println("failed to set cipher_page_size pragma")
|
||||
return err
|
||||
}
|
||||
if _, err := conn.Exec("PRAGMA cipher_hmac_algorithm = HMAC_SHA1", nil); err != nil {
|
||||
fmt.Println("failed to set cipher_hmac_algorithm pragma")
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := conn.Exec("PRAGMA cipher_kdf_algorithm = PBKDF2_HMAC_SHA1", nil); err != nil {
|
||||
fmt.Println("failed to set cipher_kdf_algorithm pragma")
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := conn.Exec(fmt.Sprintf("PRAGMA kdf_iter = '%d'", kdfIterationsNumber), []driver.Value{}); err != nil {
|
||||
return errors.New("failed to set `kdf_iter` pragma")
|
||||
}
|
||||
|
||||
// readers do not block writers and faster i/o operations
|
||||
if _, err := conn.Exec("PRAGMA journal_mode=WAL", []driver.Value{}); err != nil && path != InMemoryPath {
|
||||
return fmt.Errorf("failed to set `journal_mode` pragma: %w", err)
|
||||
}
|
||||
|
||||
// workaround to mitigate the issue of "database is locked" errors during concurrent write operations
|
||||
if _, err := conn.Exec("PRAGMA busy_timeout=60000", []driver.Value{}); err != nil {
|
||||
return errors.New("failed to set `busy_timeout` pragma")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
dsn, err := buildSqlcipherDSN(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
db, err := sql.Open(driverName, dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if path == InMemoryPath {
|
||||
db.SetMaxOpenConns(1)
|
||||
} else {
|
||||
nproc := func() int {
|
||||
maxProcs := runtime.GOMAXPROCS(0)
|
||||
numCPU := runtime.NumCPU()
|
||||
if maxProcs < numCPU {
|
||||
return maxProcs
|
||||
}
|
||||
return numCPU
|
||||
}()
|
||||
db.SetMaxOpenConns(nproc)
|
||||
db.SetMaxIdleConns(nproc)
|
||||
}
|
||||
|
||||
// Dummy select to check if the key is correct. Will return last error from initialization
|
||||
if _, err := db.Exec("SELECT 'Key check'"); err != nil {
|
||||
db.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// OpenDB opens encrypted database.
|
||||
func OpenDB(path string, key string, kdfIterationsNumber int) (*sql.DB, error) {
|
||||
return openDB(path, key, kdfIterationsNumber, V4CipherPageSize)
|
||||
}
|
||||
|
||||
// OpenUnecryptedDB opens database with setting PRAGMA key.
|
||||
func OpenUnecryptedDB(path string) (*sql.DB, error) {
|
||||
db, err := sql.Open("sqlite3", path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
// readers do not block writers and faster i/o operations
|
||||
// https://www.sqlite.org/draft/wal.html
|
||||
// must be set after db is encrypted
|
||||
if path != InMemoryPath {
|
||||
var mode string
|
||||
err = db.QueryRow("PRAGMA journal_mode=WAL").Scan(&mode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mode != WALMode {
|
||||
return nil, fmt.Errorf("unable to set journal_mode to WAL. actual mode %s", mode)
|
||||
}
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func ChangeEncryptionKey(path string, key string, kdfIterationsNumber int, newKey string, onStart func(), onEnd func()) error {
|
||||
if onStart != nil {
|
||||
onStart()
|
||||
}
|
||||
|
||||
if onEnd != nil {
|
||||
defer onEnd()
|
||||
}
|
||||
|
||||
if kdfIterationsNumber <= 0 {
|
||||
kdfIterationsNumber = dbsetup.ReducedKDFIterationsNumber
|
||||
}
|
||||
|
||||
db, err := openDB(path, key, kdfIterationsNumber, V4CipherPageSize)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resetKeyString := fmt.Sprintf("PRAGMA rekey = '%s'", newKey)
|
||||
if _, err = db.Exec(resetKeyString); err != nil {
|
||||
return errors.New("failed to set rekey pragma")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MigrateV3ToV4 migrates database from v3 to v4 format with encryption.
|
||||
func MigrateV3ToV4(v3Path string, v4Path string, key string, kdfIterationsNumber int, onStart func(), onEnd func()) error {
|
||||
|
||||
db, err := openDB(v3Path, key, kdfIterationsNumber, V3CipherPageSize)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("failed to open db", err)
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
return encryptDB(db, v4Path, key, kdfIterationsNumber, onStart, onEnd)
|
||||
}
|
||||
Reference in New Issue
Block a user