diff --git a/api/geth_backend.go b/api/geth_backend.go index b1278f445..f37a04c34 100644 --- a/api/geth_backend.go +++ b/api/geth_backend.go @@ -400,6 +400,60 @@ func (b *GethStatusBackend) StartNodeWithAccount(acc multiaccounts.Account, pass return err } +func (b *GethStatusBackend) ExportUnencryptedDatabase(acc multiaccounts.Account, password, directory string) error { + b.mu.Lock() + defer b.mu.Unlock() + if b.appDB != nil { + return nil + } + if len(b.rootDataDir) == 0 { + return errors.New("root datadir wasn't provided") + } + + // Migrate file path to fix issue https://github.com/status-im/status-go/issues/2027 + oldPath := filepath.Join(b.rootDataDir, fmt.Sprintf("app-%x.sql", acc.KeyUID)) + newPath := filepath.Join(b.rootDataDir, fmt.Sprintf("%s.db", acc.KeyUID)) + + _, err := os.Stat(oldPath) + if err == nil { + err := os.Rename(oldPath, newPath) + if err != nil { + return err + } + + // rename journals as well, but ignore errors + _ = os.Rename(oldPath+"-shm", newPath+"-shm") + _ = os.Rename(oldPath+"-wal", newPath+"-wal") + } + + err = appdatabase.DecryptDatabase(newPath, directory, password) + if err != nil { + b.log.Error("failed to initialize db", "err", err) + return err + } + return nil +} + +func (b *GethStatusBackend) ImportUnencryptedDatabase(acc multiaccounts.Account, password, databasePath string) error { + b.mu.Lock() + defer b.mu.Unlock() + if b.appDB != nil { + return nil + } + if len(b.rootDataDir) == 0 { + return errors.New("root datadir wasn't provided") + } + + path := filepath.Join(b.rootDataDir, fmt.Sprintf("%s.db", acc.KeyUID)) + + err := appdatabase.EncryptDatabase(databasePath, path, password) + if err != nil { + b.log.Error("failed to initialize db", "err", err) + return err + } + return nil +} + func (b *GethStatusBackend) SaveAccountAndStartNodeWithKey(acc multiaccounts.Account, password string, settings accounts.Settings, nodecfg *params.NodeConfig, subaccs []accounts.Account, keyHex string) error { err := b.SaveAccount(acc) if err != nil { diff --git a/appdatabase/database.go b/appdatabase/database.go index ec360d872..9ed0f801b 100644 --- a/appdatabase/database.go +++ b/appdatabase/database.go @@ -19,3 +19,15 @@ func InitializeDB(path, password string) (*sql.DB, error) { } return db, nil } + +// DecryptDatabase creates an unencrypted copy of the database and copies it +// over to the given directory +func DecryptDatabase(oldPath, newPath, password string) error { + return sqlite.DecryptDB(oldPath, newPath, password) +} + +// EncryptDatabase creates an encrypted copy of the database and copies it to the +// user path +func EncryptDatabase(oldPath, newPath, password string) error { + return sqlite.EncryptDB(oldPath, newPath, password) +} diff --git a/mobile/status.go b/mobile/status.go index 69f865a67..6a39ea796 100644 --- a/mobile/status.go +++ b/mobile/status.go @@ -628,3 +628,31 @@ func MultiformatDeserializePublicKey(key, outBase string) string { return pk } + +// ExportUnencryptedDatabase exports the database unencrypted to the given path +func ExportUnencryptedDatabase(accountData, password, databasePath string) string { + var account multiaccounts.Account + err := json.Unmarshal([]byte(accountData), &account) + if err != nil { + return makeJSONResponse(err) + } + err = statusBackend.ExportUnencryptedDatabase(account, password, databasePath) + if err != nil { + return makeJSONResponse(err) + } + return makeJSONResponse(nil) +} + +// ImportUnencryptedDatabase imports the database unencrypted to the given directory +func ImportUnencryptedDatabase(accountData, password, databasePath string) string { + var account multiaccounts.Account + err := json.Unmarshal([]byte(accountData), &account) + if err != nil { + return makeJSONResponse(err) + } + err = statusBackend.ImportUnencryptedDatabase(account, password, databasePath) + if err != nil { + return makeJSONResponse(err) + } + return makeJSONResponse(nil) +} diff --git a/sqlite/sqlite.go b/sqlite/sqlite.go index 3a1fb1bdf..37ad3392a 100644 --- a/sqlite/sqlite.go +++ b/sqlite/sqlite.go @@ -4,6 +4,7 @@ import ( "database/sql" "errors" "fmt" + "os" _ "github.com/mutecomm/go-sqlcipher" // We require go sqlcipher that overrides default implementation ) @@ -18,6 +19,55 @@ const ( WALMode = "wal" ) +// DecryptDB completely removes the encryption from the db +func DecryptDB(oldPath, newPath, key string) error { + + db, err := openDB(oldPath, key) + 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 +func EncryptDB(unencryptedPath, encryptedPath, key string) error { + + _ = 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 + } + + _, 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 +} + func openDB(path, key string) (*sql.DB, error) { db, err := sql.Open("sqlite3", path) if err != nil {