controller: add db passphrase test command (#3788)
* controller: add passphrase test * refactor --------- Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
This commit is contained in:
parent
d83a6b7133
commit
364b62320b
@ -604,6 +604,7 @@ processChatCommand' vr = \case
|
|||||||
pure $ CRArchiveImported fileErrs
|
pure $ CRArchiveImported fileErrs
|
||||||
APIDeleteStorage -> withStoreChanged deleteStorage
|
APIDeleteStorage -> withStoreChanged deleteStorage
|
||||||
APIStorageEncryption cfg -> withStoreChanged $ sqlCipherExport cfg
|
APIStorageEncryption cfg -> withStoreChanged $ sqlCipherExport cfg
|
||||||
|
TestStorageEncryption key -> withStoreChanged $ sqlCipherTestKey key
|
||||||
ExecChatStoreSQL query -> CRSQLResult <$> withStore' (`execSQL` query)
|
ExecChatStoreSQL query -> CRSQLResult <$> withStore' (`execSQL` query)
|
||||||
ExecAgentStoreSQL query -> CRSQLResult <$> withAgent (`execAgentStoreSQL` query)
|
ExecAgentStoreSQL query -> CRSQLResult <$> withAgent (`execAgentStoreSQL` query)
|
||||||
SlowSQLQueries -> do
|
SlowSQLQueries -> do
|
||||||
@ -6510,6 +6511,7 @@ chatCommandP =
|
|||||||
"/db encrypt " *> (APIStorageEncryption . dbEncryptionConfig "" <$> dbKeyP),
|
"/db encrypt " *> (APIStorageEncryption . dbEncryptionConfig "" <$> dbKeyP),
|
||||||
"/db key " *> (APIStorageEncryption <$> (dbEncryptionConfig <$> dbKeyP <* A.space <*> dbKeyP)),
|
"/db key " *> (APIStorageEncryption <$> (dbEncryptionConfig <$> dbKeyP <* A.space <*> dbKeyP)),
|
||||||
"/db decrypt " *> (APIStorageEncryption . (`dbEncryptionConfig` "") <$> dbKeyP),
|
"/db decrypt " *> (APIStorageEncryption . (`dbEncryptionConfig` "") <$> dbKeyP),
|
||||||
|
"/db test key " *> (TestStorageEncryption <$> dbKeyP),
|
||||||
"/sql chat " *> (ExecChatStoreSQL <$> textP),
|
"/sql chat " *> (ExecChatStoreSQL <$> textP),
|
||||||
"/sql agent " *> (ExecAgentStoreSQL <$> textP),
|
"/sql agent " *> (ExecAgentStoreSQL <$> textP),
|
||||||
"/sql slow" $> SlowSQLQueries,
|
"/sql slow" $> SlowSQLQueries,
|
||||||
|
@ -9,6 +9,7 @@ module Simplex.Chat.Archive
|
|||||||
importArchive,
|
importArchive,
|
||||||
deleteStorage,
|
deleteStorage,
|
||||||
sqlCipherExport,
|
sqlCipherExport,
|
||||||
|
sqlCipherTestKey,
|
||||||
archiveFilesFolder,
|
archiveFilesFolder,
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
@ -20,6 +21,7 @@ import Control.Monad.Reader
|
|||||||
import qualified Data.ByteArray as BA
|
import qualified Data.ByteArray as BA
|
||||||
import Data.Functor (($>))
|
import Data.Functor (($>))
|
||||||
import Data.Maybe (fromMaybe)
|
import Data.Maybe (fromMaybe)
|
||||||
|
import Data.Text (Text)
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import qualified Database.SQLite3 as SQL
|
import qualified Database.SQLite3 as SQL
|
||||||
import Simplex.Chat.Controller
|
import Simplex.Chat.Controller
|
||||||
@ -147,19 +149,8 @@ sqlCipherExport DBEncryptionConfig {currentKey = DBEncryptionKey key, newKey = D
|
|||||||
atomically $ writeTVar dbKey $ storeKey key' (fromMaybe False keepKey)
|
atomically $ writeTVar dbKey $ storeKey key' (fromMaybe False keepKey)
|
||||||
export f = do
|
export f = do
|
||||||
withDB f (`SQL.exec` exportSQL) DBErrorExport
|
withDB f (`SQL.exec` exportSQL) DBErrorExport
|
||||||
withDB (exported f) (`SQL.exec` testSQL) DBErrorOpen
|
withDB (exported f) (`SQL.exec` testSQL key') DBErrorOpen
|
||||||
where
|
where
|
||||||
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)
|
|
||||||
where
|
|
||||||
checkSQLError e = case SQL.sqlError e of
|
|
||||||
SQL.ErrorNotADatabase -> pure $ Just SQLiteErrorNotADatabase
|
|
||||||
_ -> sqliteError' e
|
|
||||||
sqliteError' :: Show e => e -> m (Maybe SQLiteError)
|
|
||||||
sqliteError' = pure . Just . SQLiteError . show
|
|
||||||
exportSQL =
|
exportSQL =
|
||||||
T.unlines $
|
T.unlines $
|
||||||
keySQL key
|
keySQL key
|
||||||
@ -167,14 +158,38 @@ sqlCipherExport DBEncryptionConfig {currentKey = DBEncryptionKey key, newKey = D
|
|||||||
"SELECT sqlcipher_export('exported');",
|
"SELECT sqlcipher_export('exported');",
|
||||||
"DETACH DATABASE exported;"
|
"DETACH DATABASE exported;"
|
||||||
]
|
]
|
||||||
testSQL =
|
|
||||||
T.unlines $
|
withDB :: forall a m. ChatMonad m => FilePath -> (SQL.Database -> IO a) -> (SQLiteError -> DatabaseError) -> m ()
|
||||||
keySQL key'
|
withDB f' a err =
|
||||||
<> [ "PRAGMA foreign_keys = ON;",
|
liftIO (bracket (SQL.open $ T.pack f') SQL.close a $> Nothing)
|
||||||
"PRAGMA secure_delete = ON;",
|
`catch` checkSQLError
|
||||||
"SELECT count(*) FROM sqlite_master;"
|
`catch` (\(e :: SomeException) -> sqliteError' e)
|
||||||
]
|
>>= mapM_ (throwDBError . err)
|
||||||
keySQL k = ["PRAGMA key = " <> keyString k <> ";" | not (BA.null k)]
|
where
|
||||||
|
checkSQLError e = case SQL.sqlError e of
|
||||||
|
SQL.ErrorNotADatabase -> pure $ Just SQLiteErrorNotADatabase
|
||||||
|
_ -> sqliteError' e
|
||||||
|
sqliteError' :: Show e => e -> m (Maybe SQLiteError)
|
||||||
|
sqliteError' = pure . Just . SQLiteError . show
|
||||||
|
|
||||||
|
testSQL :: BA.ScrubbedBytes -> Text
|
||||||
|
testSQL k =
|
||||||
|
T.unlines $
|
||||||
|
keySQL k
|
||||||
|
<> [ "PRAGMA foreign_keys = ON;",
|
||||||
|
"PRAGMA secure_delete = ON;",
|
||||||
|
"SELECT count(*) FROM sqlite_master;"
|
||||||
|
]
|
||||||
|
|
||||||
|
keySQL :: BA.ScrubbedBytes -> [Text]
|
||||||
|
keySQL k = ["PRAGMA key = " <> keyString k <> ";" | not (BA.null k)]
|
||||||
|
|
||||||
|
sqlCipherTestKey :: forall m. ChatMonad m => DBEncryptionKey -> m ()
|
||||||
|
sqlCipherTestKey (DBEncryptionKey key) = do
|
||||||
|
fs <- storageFiles
|
||||||
|
testKey `withDBs` fs
|
||||||
|
where
|
||||||
|
testKey f = withDB f (`SQL.exec` testSQL key) DBErrorOpen
|
||||||
|
|
||||||
withDBs :: Monad m => (FilePath -> m b) -> StorageFiles -> m b
|
withDBs :: Monad m => (FilePath -> m b) -> StorageFiles -> m b
|
||||||
action `withDBs` StorageFiles {chatStore, agentStore} = action (dbFilePath chatStore) >> action (dbFilePath agentStore)
|
action `withDBs` StorageFiles {chatStore, agentStore} = action (dbFilePath chatStore) >> action (dbFilePath agentStore)
|
||||||
|
@ -250,6 +250,7 @@ data ChatCommand
|
|||||||
| APIImportArchive ArchiveConfig
|
| APIImportArchive ArchiveConfig
|
||||||
| APIDeleteStorage
|
| APIDeleteStorage
|
||||||
| APIStorageEncryption DBEncryptionConfig
|
| APIStorageEncryption DBEncryptionConfig
|
||||||
|
| TestStorageEncryption DBEncryptionKey
|
||||||
| ExecChatStoreSQL Text
|
| ExecChatStoreSQL Text
|
||||||
| ExecAgentStoreSQL Text
|
| ExecAgentStoreSQL Text
|
||||||
| SlowSQLQueries
|
| SlowSQLQueries
|
||||||
|
@ -1124,6 +1124,10 @@ testDatabaseEncryption tmp = do
|
|||||||
testChatWorking alice bob
|
testChatWorking alice bob
|
||||||
alice ##> "/_stop"
|
alice ##> "/_stop"
|
||||||
alice <## "chat stopped"
|
alice <## "chat stopped"
|
||||||
|
alice ##> "/db test key wrongkey"
|
||||||
|
alice <## "error opening database after encryption: wrong passphrase or invalid database file"
|
||||||
|
alice ##> "/db test key mykey"
|
||||||
|
alice <## "ok"
|
||||||
alice ##> "/db key wrongkey nextkey"
|
alice ##> "/db key wrongkey nextkey"
|
||||||
alice <## "error encrypting database: wrong passphrase or invalid database file"
|
alice <## "error encrypting database: wrong passphrase or invalid database file"
|
||||||
alice ##> "/db key mykey nextkey"
|
alice ##> "/db key mykey nextkey"
|
||||||
|
Loading…
Reference in New Issue
Block a user