2019-06-14 10:16:30 +00:00
|
|
|
package sqlite
|
|
|
|
|
|
|
|
import (
|
|
|
|
"database/sql"
|
2023-05-18 21:42:22 +00:00
|
|
|
"database/sql/driver"
|
2019-06-14 10:16:30 +00:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
2021-01-07 11:15:02 +00:00
|
|
|
"os"
|
2023-05-18 21:42:22 +00:00
|
|
|
"runtime"
|
2019-06-14 10:16:30 +00:00
|
|
|
|
2023-05-18 21:42:22 +00:00
|
|
|
sqlcipher "github.com/mutecomm/go-sqlcipher" // We require go sqlcipher that overrides default implementation
|
2022-09-27 20:27:20 +00:00
|
|
|
|
|
|
|
"github.com/status-im/status-go/protocol/sqlite"
|
2019-06-14 10:16:30 +00:00
|
|
|
)
|
|
|
|
|
2019-08-20 15:38:40 +00:00
|
|
|
const (
|
|
|
|
// The reduced number of kdf iterations (for performance reasons) which is
|
2022-09-27 20:27:20 +00:00
|
|
|
// used as the default value
|
2019-08-20 15:38:40 +00:00
|
|
|
// https://github.com/status-im/status-go/pull/1343
|
|
|
|
// https://notes.status.im/i8Y_l7ccTiOYq09HVgoFwA
|
2022-09-27 20:27:20 +00:00
|
|
|
ReducedKDFIterationsNumber = 3200
|
|
|
|
|
2019-08-20 15:38:40 +00:00
|
|
|
// WALMode for sqlite.
|
2021-03-31 16:23:45 +00:00
|
|
|
WALMode = "wal"
|
2023-03-06 13:26:46 +00:00
|
|
|
InMemoryPath = ":memory:"
|
2019-08-20 15:38:40 +00:00
|
|
|
)
|
2019-07-31 10:53:51 +00:00
|
|
|
|
2021-01-07 11:15:02 +00:00
|
|
|
// DecryptDB completely removes the encryption from the db
|
2022-09-27 20:27:20 +00:00
|
|
|
func DecryptDB(oldPath string, newPath string, key string, kdfIterationsNumber int) error {
|
2021-01-07 11:15:02 +00:00
|
|
|
|
2022-09-27 20:27:20 +00:00
|
|
|
db, err := openDB(oldPath, key, kdfIterationsNumber)
|
2021-01-07 11:15:02 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// EncryptDB takes a plaintext database and adds encryption
|
2022-09-27 20:27:20 +00:00
|
|
|
func EncryptDB(unencryptedPath string, encryptedPath string, key string, kdfIterationsNumber int) error {
|
2021-01-07 11:15:02 +00:00
|
|
|
_ = os.Remove(encryptedPath)
|
|
|
|
|
|
|
|
db, err := OpenUnecryptedDB(unencryptedPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = db.Exec(`ATTACH DATABASE '` + encryptedPath + `' AS encrypted KEY '` + key + `'`)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-09-27 20:27:20 +00:00
|
|
|
if kdfIterationsNumber <= 0 {
|
|
|
|
kdfIterationsNumber = sqlite.ReducedKDFIterationsNumber
|
|
|
|
}
|
|
|
|
|
2021-01-07 11:15:02 +00:00
|
|
|
_, err = db.Exec(fmt.Sprintf("PRAGMA encrypted.kdf_iter = '%d'", kdfIterationsNumber))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = db.Exec(`SELECT sqlcipher_export('encrypted')`)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
_, err = db.Exec(`DETACH DATABASE encrypted`)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-04-25 12:00:17 +00:00
|
|
|
func openDB(path string, key string, kdfIterationsNumber int) (*sql.DB, error) {
|
2023-05-18 21:42:22 +00:00
|
|
|
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 = sqlite.ReducedKDFIterationsNumber
|
|
|
|
}
|
|
|
|
|
|
|
|
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 errors.New("failed to set `journal_mode` pragma")
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
db, err := sql.Open(driverName, path)
|
2019-06-14 10:16:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-04-25 12:00:17 +00:00
|
|
|
|
2023-05-18 21:42:22 +00:00
|
|
|
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)
|
2019-06-14 10:16:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return db, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// OpenDB opens not-encrypted database.
|
2022-09-27 20:27:20 +00:00
|
|
|
func OpenDB(path string, key string, kdfIterationsNumber int) (*sql.DB, error) {
|
|
|
|
return openDB(path, key, kdfIterationsNumber)
|
2019-06-14 10:16:30 +00:00
|
|
|
}
|
2019-08-20 15:38:40 +00:00
|
|
|
|
|
|
|
// 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
|
|
|
|
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
|
|
|
|
}
|
2021-06-23 09:21:21 +00:00
|
|
|
|
2022-09-27 20:27:20 +00:00
|
|
|
func ChangeEncryptionKey(path string, key string, kdfIterationsNumber int, newKey string) error {
|
|
|
|
if kdfIterationsNumber <= 0 {
|
|
|
|
kdfIterationsNumber = sqlite.ReducedKDFIterationsNumber
|
|
|
|
}
|
|
|
|
|
|
|
|
db, err := openDB(path, key, kdfIterationsNumber)
|
2021-06-23 09:21:21 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|