core: close stores before import/delete/encryption operations to make compatible with windows, make encryption more resilient (#3146)

* core: close stores before import/delete/encryption operations to make compatible with windows, make encryption more resilient

* remove file names

* do not remove files if already removed
This commit is contained in:
Evgeny Poberezkin
2023-09-29 13:09:48 +01:00
committed by GitHub
parent 1d34500fba
commit 70a65e8969
4 changed files with 52 additions and 40 deletions

View File

@@ -9,7 +9,7 @@ constraints: zip +disable-bzip2 +disable-zstd
source-repository-package
type: git
location: https://github.com/simplex-chat/simplexmq.git
tag: 8d47f690838371bc848e4b31a4b09ef6bf67ccc5
tag: ec1b72cb8013a65a5d9783104a47ae44f5730089
source-repository-package
type: git

View File

@@ -1,5 +1,5 @@
{
"https://github.com/simplex-chat/simplexmq.git"."8d47f690838371bc848e4b31a4b09ef6bf67ccc5" = "1pwasv22ii3wy4xchaknlwczmy5ws7adx7gg2g58lxzrgdjm3650";
"https://github.com/simplex-chat/simplexmq.git"."ec1b72cb8013a65a5d9783104a47ae44f5730089" = "1lz5rvgxp242zg95r9zd9j50y45314cf8nfpjg1qsa55nrk2w19b";
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
"https://github.com/kazu-yamamoto/http2.git"."b5a1b7200cf5bc7044af34ba325284271f6dff25" = "0dqb50j57an64nf4qcf5vcz4xkd1vzvghvf8bk529c1k30r9nfzb";
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "0kiwhvml42g9anw4d2v0zd1fpc790pj9syg5x3ik4l97fnkbbwpp";

View File

@@ -21,7 +21,7 @@ import qualified Data.Text as T
import qualified Database.SQLite3 as SQL
import Simplex.Chat.Controller
import Simplex.Messaging.Agent.Client (agentClientStore)
import Simplex.Messaging.Agent.Store.SQLite (SQLiteStore (..), sqlString)
import Simplex.Messaging.Agent.Store.SQLite (SQLiteStore (..), sqlString, closeSQLiteStore)
import Simplex.Messaging.Util
import System.FilePath
import UnliftIO.Directory
@@ -42,9 +42,9 @@ archiveFilesFolder = "simplex_v1_files"
exportArchive :: ChatMonad m => ArchiveConfig -> m ()
exportArchive cfg@ArchiveConfig {archivePath, disableCompression} =
withTempDir cfg "simplex-chat." $ \dir -> do
StorageFiles {chatDb, agentDb, filesPath} <- storageFiles
copyFile chatDb $ dir </> archiveChatDbFile
copyFile agentDb $ dir </> archiveAgentDbFile
StorageFiles {chatStore, agentStore, filesPath} <- storageFiles
copyFile (dbFilePath chatStore) $ dir </> archiveChatDbFile
copyFile (dbFilePath agentStore) $ dir </> archiveAgentDbFile
forM_ filesPath $ \fp ->
copyDirectoryFiles fp $ dir </> archiveFilesFolder
let method = if disableCompression == Just True then Z.Store else Z.Deflate
@@ -54,11 +54,11 @@ importArchive :: ChatMonad m => ArchiveConfig -> m [ArchiveError]
importArchive cfg@ArchiveConfig {archivePath} =
withTempDir cfg "simplex-chat." $ \dir -> do
Z.withArchive archivePath $ Z.unpackInto dir
StorageFiles {chatDb, agentDb, filesPath} <- storageFiles
backup chatDb
backup agentDb
copyFile (dir </> archiveChatDbFile) chatDb
copyFile (dir </> archiveAgentDbFile) agentDb
fs@StorageFiles {chatStore, agentStore, filesPath} <- storageFiles
liftIO $ closeSQLiteStore `withStores` fs
backup `withDBs` fs
copyFile (dir </> archiveChatDbFile) $ dbFilePath chatStore
copyFile (dir </> archiveAgentDbFile) $ dbFilePath agentStore
copyFiles dir filesPath
`E.catch` \(e :: E.SomeException) -> pure [AEImport . ChatError . CEException $ show e]
where
@@ -94,53 +94,60 @@ copyDirectoryFiles fromDir toDir = do
deleteStorage :: ChatMonad m => m ()
deleteStorage = do
StorageFiles {chatDb, agentDb, filesPath} <- storageFiles
removeFile chatDb
removeFile agentDb
mapM_ removePathForcibly filesPath
tmpPath <- readTVarIO =<< asks tempDirectory
mapM_ removePathForcibly tmpPath
fs <- storageFiles
liftIO $ closeSQLiteStore `withStores` fs
remove `withDBs` fs
mapM_ removeDir $ filesPath fs
mapM_ removeDir =<< chatReadVar tempDirectory
where
remove f = whenM (doesFileExist f) $ removeFile f
removeDir d = whenM (doesDirectoryExist d) $ removePathForcibly d
data StorageFiles = StorageFiles
{ chatDb :: FilePath,
chatEncrypted :: TVar Bool,
agentDb :: FilePath,
agentEncrypted :: TVar Bool,
{ chatStore :: SQLiteStore,
agentStore :: SQLiteStore,
filesPath :: Maybe FilePath
}
storageFiles :: ChatMonad m => m StorageFiles
storageFiles = do
ChatController {chatStore, filesFolder, smpAgent} <- ask
let SQLiteStore {dbFilePath = chatDb, dbEncrypted = chatEncrypted} = chatStore
SQLiteStore {dbFilePath = agentDb, dbEncrypted = agentEncrypted} = agentClientStore smpAgent
let agentStore = agentClientStore smpAgent
filesPath <- readTVarIO filesFolder
pure StorageFiles {chatDb, chatEncrypted, agentDb, agentEncrypted, filesPath}
pure StorageFiles {chatStore, agentStore, filesPath}
sqlCipherExport :: forall m. ChatMonad m => DBEncryptionConfig -> m ()
sqlCipherExport DBEncryptionConfig {currentKey = DBEncryptionKey key, newKey = DBEncryptionKey key'} =
when (key /= key') $ do
fs@StorageFiles {chatDb, chatEncrypted, agentDb, agentEncrypted} <- storageFiles
checkFile `with` fs
backup `with` fs
(export chatDb chatEncrypted >> export agentDb agentEncrypted)
`catchChatError` \e -> (restore `with` fs) >> throwError e
fs <- storageFiles
checkFile `withDBs` fs
backup `withDBs` fs
checkEncryption `withStores` fs
removeExported `withDBs` fs
export `withDBs` fs
-- closing after encryption prevents closing in case wrong encryption key was passed
liftIO $ closeSQLiteStore `withStores` fs
(moveExported `withStores` fs)
`catchChatError` \e -> (restore `withDBs` fs) >> throwError e
where
action `with` StorageFiles {chatDb, agentDb} = action chatDb >> action agentDb
backup f = copyFile f (f <> ".bak")
restore f = copyFile (f <> ".bak") f
checkFile f = unlessM (doesFileExist f) $ throwDBError $ DBErrorNoFile f
export f dbEnc = do
enc <- readTVarIO dbEnc
checkEncryption SQLiteStore {dbEncrypted} = do
enc <- readTVarIO dbEncrypted
when (enc && null key) $ throwDBError DBErrorEncrypted
when (not enc && not (null key)) $ throwDBError DBErrorPlaintext
withDB (`SQL.exec` exportSQL) DBErrorExport
renameFile (f <> ".exported") f
withDB (`SQL.exec` testSQL) DBErrorOpen
atomically $ writeTVar dbEnc $ not (null key')
exported = (<> ".exported")
removeExported f = whenM (doesFileExist $ exported f) $ removeFile (exported f)
moveExported SQLiteStore {dbFilePath = f, dbEncrypted} = do
renameFile (exported f) f
atomically $ writeTVar dbEncrypted $ not (null key')
export f = do
withDB f (`SQL.exec` exportSQL) DBErrorExport
withDB (exported f) (`SQL.exec` testSQL) DBErrorOpen
where
withDB a err =
liftIO (bracket (SQL.open $ T.pack f) SQL.close a $> Nothing)
withDB f' a err =
liftIO (bracket (SQL.open $ T.pack f') SQL.close a $> Nothing)
`catch` checkSQLError
`catch` (\(e :: SomeException) -> sqliteError' e)
>>= mapM_ (throwDBError . err)
@@ -162,7 +169,12 @@ sqlCipherExport DBEncryptionConfig {currentKey = DBEncryptionKey key, newKey = D
keySQL key'
<> [ "PRAGMA foreign_keys = ON;",
"PRAGMA secure_delete = ON;",
"PRAGMA auto_vacuum = FULL;",
"SELECT count(*) FROM sqlite_master;"
]
keySQL k = ["PRAGMA key = " <> sqlString k <> ";" | not (null k)]
withDBs :: Monad m => (FilePath -> m b) -> StorageFiles -> m b
action `withDBs` StorageFiles {chatStore, agentStore} = action (dbFilePath chatStore) >> action (dbFilePath agentStore)
withStores :: Monad m => (SQLiteStore -> m b) -> StorageFiles -> m b
action `withStores` StorageFiles {chatStore, agentStore} = action chatStore >> action agentStore

View File

@@ -49,7 +49,7 @@ extra-deps:
# - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561
# - ../simplexmq
- github: simplex-chat/simplexmq
commit: 8d47f690838371bc848e4b31a4b09ef6bf67ccc5
commit: ec1b72cb8013a65a5d9783104a47ae44f5730089
- github: kazu-yamamoto/http2
commit: b5a1b7200cf5bc7044af34ba325284271f6dff25
# - ../direct-sqlcipher