245 lines
5.0 KiB
Go
245 lines
5.0 KiB
Go
package db
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"os"
|
|
|
|
sqlite "github.com/mutecomm/go-sqlcipher" // We require go sqlcipher that overrides default implementation
|
|
"github.com/status-im/migrate/v4"
|
|
"github.com/status-im/migrate/v4/database/sqlcipher"
|
|
"github.com/status-im/migrate/v4/source/go_bindata"
|
|
"github.com/status-im/status-go/messaging/db/migrations"
|
|
)
|
|
|
|
const exportDB = "SELECT sqlcipher_export('newdb')"
|
|
|
|
// The default number of kdf iterations in sqlcipher (from version 3.0.0)
|
|
// https://github.com/sqlcipher/sqlcipher/blob/fda4c68bb474da7e955be07a2b807bda1bb19bd2/CHANGELOG.md#300---2013-11-05
|
|
// https://www.zetetic.net/sqlcipher/sqlcipher-api/#kdf_iter
|
|
const defaultKdfIterationsNumber = 64000
|
|
|
|
// 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 KdfIterationsNumber = 3200
|
|
|
|
func MigrateDBFile(oldPath string, newPath string, oldKey string, newKey string) error {
|
|
_, err := os.Stat(oldPath)
|
|
|
|
// No files, nothing to do
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
|
|
// Any other error, throws
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := os.Rename(oldPath, newPath); err != nil {
|
|
return err
|
|
}
|
|
|
|
db, err := Open(newPath, oldKey, defaultKdfIterationsNumber)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
keyString := fmt.Sprintf("PRAGMA rekey = '%s'", newKey)
|
|
|
|
if _, err = db.Exec(keyString); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
// MigrateDBKeyKdfIterations changes the number of kdf iterations executed
|
|
// during the database key derivation. This change is necessary because
|
|
// of performance reasons.
|
|
// https://github.com/status-im/status-go/pull/1343
|
|
// `sqlcipher_export` is used for migration, check out this link for details:
|
|
// https://www.zetetic.net/sqlcipher/sqlcipher-api/#sqlcipher_export
|
|
func MigrateDBKeyKdfIterations(oldPath string, newPath string, key string) error {
|
|
_, err := os.Stat(oldPath)
|
|
|
|
// No files, nothing to do
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
|
|
// Any other error, throws
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
isEncrypted, err := sqlite.IsEncrypted(oldPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Nothing to do, move db to the next migration
|
|
if !isEncrypted {
|
|
return os.Rename(oldPath, newPath)
|
|
}
|
|
|
|
db, err := Open(oldPath, key, defaultKdfIterationsNumber)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
attach := fmt.Sprintf(
|
|
"ATTACH DATABASE '%s' AS newdb KEY '%s'",
|
|
newPath,
|
|
key)
|
|
|
|
if _, err = db.Exec(attach); err != nil {
|
|
return err
|
|
}
|
|
|
|
changeKdfIter := fmt.Sprintf(
|
|
"PRAGMA newdb.kdf_iter = %d",
|
|
KdfIterationsNumber)
|
|
|
|
if _, err = db.Exec(changeKdfIter); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err = db.Exec(exportDB); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = db.Close(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.Remove(oldPath)
|
|
}
|
|
|
|
// EncryptDatabase encrypts an unencrypted database with key
|
|
func EncryptDatabase(oldPath string, newPath string, key string) error {
|
|
_, err := os.Stat(oldPath)
|
|
|
|
// No files, nothing to do
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
|
|
// Any other error, throws
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
isEncrypted, err := sqlite.IsEncrypted(oldPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Nothing to do, already encrypted
|
|
if isEncrypted {
|
|
return os.Rename(oldPath, newPath)
|
|
}
|
|
|
|
db, err := Open(oldPath, "", defaultKdfIterationsNumber)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
attach := fmt.Sprintf(
|
|
"ATTACH DATABASE '%s' AS newdb KEY '%s'",
|
|
newPath,
|
|
key)
|
|
|
|
if _, err = db.Exec(attach); err != nil {
|
|
return err
|
|
}
|
|
|
|
changeKdfIter := fmt.Sprintf(
|
|
"PRAGMA newdb.kdf_iter = %d",
|
|
KdfIterationsNumber)
|
|
|
|
if _, err = db.Exec(changeKdfIter); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err = db.Exec(exportDB); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = db.Close(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.Remove(oldPath)
|
|
}
|
|
|
|
func migrateDB(db *sql.DB) error {
|
|
resources := bindata.Resource(
|
|
migrations.AssetNames(),
|
|
func(name string) ([]byte, error) {
|
|
return migrations.Asset(name)
|
|
},
|
|
)
|
|
|
|
source, err := bindata.WithInstance(resources)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
driver, err := sqlcipher.WithInstance(db, &sqlcipher.Config{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
m, err := migrate.NewWithInstance(
|
|
"go-bindata",
|
|
source,
|
|
"sqlcipher",
|
|
driver)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = m.Up(); err != migrate.ErrNoChange {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func Open(path string, key string, kdfIter int) (*sql.DB, error) {
|
|
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
|
|
}
|
|
|
|
// Migrate db
|
|
if err = migrateDB(db); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return db, nil
|
|
}
|