From 70a65e8969a67905a36116b04806aa7bd7d0c800 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Fri, 29 Sep 2023 13:09:48 +0100 Subject: [PATCH] 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 --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat/Archive.hs | 86 +++++++++++++++++++++---------------- stack.yaml | 2 +- 4 files changed, 52 insertions(+), 40 deletions(-) diff --git a/cabal.project b/cabal.project index b4024f088c..af664652db 100644 --- a/cabal.project +++ b/cabal.project @@ -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 diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 26f4ea1122..b6ca36e313 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -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"; diff --git a/src/Simplex/Chat/Archive.hs b/src/Simplex/Chat/Archive.hs index f8fa0d152a..e0de971bda 100644 --- a/src/Simplex/Chat/Archive.hs +++ b/src/Simplex/Chat/Archive.hs @@ -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 diff --git a/stack.yaml b/stack.yaml index 0840970e49..bce5dd3a68 100644 --- a/stack.yaml +++ b/stack.yaml @@ -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