diff --git a/cabal.project b/cabal.project index 05e9af91ff..4fccb51694 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: dab55e0a9b03577f643af7922afa061801d82ed5 + tag: 851ed2d02e2a78c15893ad8bc9c5a4d917eb6a35 source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 7ca6c8c074..b2e11db33a 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."dab55e0a9b03577f643af7922afa061801d82ed5" = "0dzqsvzxby83nla0rpx3xzj2y18lvmgs5ldjv5i1yp52npc88s1m"; + "https://github.com/simplex-chat/simplexmq.git"."851ed2d02e2a78c15893ad8bc9c5a4d917eb6a35" = "0rm13iknnqhdb42nmyjc2wj85z23p337bp026ihnychax5s1216j"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index 691a4101a7..792f9642d3 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -28,6 +28,7 @@ import Simplex.Chat import Simplex.Chat.Controller (ChatCommand (..), ChatConfig (..), ChatController (..), ChatDatabase (..), ChatLogLevel (..)) import Simplex.Chat.Core import Simplex.Chat.Options +import Simplex.Chat.Protocol (currentChatVersion, pqEncryptionCompressionVersion) import Simplex.Chat.Store import Simplex.Chat.Store.Profiles import Simplex.Chat.Terminal @@ -37,12 +38,13 @@ import Simplex.FileTransfer.Description (kb, mb) import Simplex.FileTransfer.Server (runXFTPServerBlocking) import Simplex.FileTransfer.Server.Env (XFTPServerConfig (..), defaultFileExpiration) import Simplex.Messaging.Agent.Env.SQLite -import Simplex.Messaging.Agent.Protocol (supportedSMPAgentVRange, pattern VersionSMPA) +import Simplex.Messaging.Agent.Protocol (currentSMPAgentVersion, duplexHandshakeSMPAgentVersion, pqdrSMPAgentVersion, supportedSMPAgentVRange) import Simplex.Messaging.Agent.RetryInterval import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation (..)) import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import Simplex.Messaging.Client (ProtocolClientConfig (..), defaultNetworkConfig) -import Simplex.Messaging.Crypto.Ratchet (supportedE2EEncryptVRange, pattern PQSupportOff, pattern VersionE2E) +import Simplex.Messaging.Crypto.Ratchet (supportedE2EEncryptVRange, pattern PQSupportOff) +import qualified Simplex.Messaging.Crypto.Ratchet as CR import Simplex.Messaging.Server (runSMPServerBlocking) import Simplex.Messaging.Server.Env.STM import Simplex.Messaging.Transport @@ -147,22 +149,38 @@ testAgentCfgVPrev = smpCfg = (smpCfg testAgentCfg) {serverVRange = prevRange $ serverVRange $ smpCfg testAgentCfg} } +testAgentCfgVNext :: AgentConfig +testAgentCfgVNext = + testAgentCfg + { smpClientVRange = nextRange $ smpClientVRange testAgentCfg, + smpAgentVRange = \_ -> mkVersionRange duplexHandshakeSMPAgentVersion $ max pqdrSMPAgentVersion currentSMPAgentVersion, + e2eEncryptVRange = \_ -> mkVersionRange CR.kdfX3DHE2EEncryptVersion $ max CR.pqRatchetE2EEncryptVersion CR.currentE2EEncryptVersion, + smpCfg = (smpCfg testAgentCfg) {serverVRange = nextRange $ serverVRange $ smpCfg testAgentCfg} + } + testAgentCfgV1 :: AgentConfig testAgentCfgV1 = testAgentCfg { smpClientVRange = v1Range, - smpAgentVRange = \_ -> versionToRange (VersionSMPA 2), -- duplexHandshakeSMPAgentVersion, - e2eEncryptVRange = \_ -> versionToRange (VersionE2E 2), -- kdfX3DHE2EEncryptVersion, + smpAgentVRange = \_ -> versionToRange duplexHandshakeSMPAgentVersion, + e2eEncryptVRange = \_ -> versionToRange CR.kdfX3DHE2EEncryptVersion, smpCfg = (smpCfg testAgentCfg) {serverVRange = versionToRange batchCmdsSMPVersion} } testCfgVPrev :: ChatConfig testCfgVPrev = testCfg - { chatVRange = prevRange . chatVRange testCfg, + { chatVRange = \_ -> prevRange $ chatVRange testCfg PQSupportOff, agentConfig = testAgentCfgVPrev } +testCfgVNext :: ChatConfig +testCfgVNext = + testCfg + { chatVRange = \_ -> mkVersionRange initialChatVersion $ max pqEncryptionCompressionVersion currentChatVersion, + agentConfig = testAgentCfgVNext + } + testCfgV1 :: ChatConfig testCfgV1 = testCfg @@ -173,12 +191,18 @@ testCfgV1 = prevRange :: VersionRange v -> VersionRange v prevRange vr = vr {maxVersion = max (minVersion vr) (prevVersion $ maxVersion vr)} +nextRange :: VersionRange v -> VersionRange v +nextRange vr = vr {maxVersion = max (minVersion vr) (nextVersion $ maxVersion vr)} + v1Range :: VersionRange v v1Range = mkVersionRange (Version 1) (Version 1) prevVersion :: Version v -> Version v prevVersion (Version v) = Version (v - 1) +nextVersion :: Version v -> Version v +nextVersion (Version v) = Version (v + 1) + testCfgCreateGroupDirect :: ChatConfig testCfgCreateGroupDirect = mkCfgCreateGroupDirect testCfg diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index f50c82878f..c80323b114 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -23,9 +23,9 @@ import Simplex.Chat.Controller (ChatConfig (..)) import Simplex.Chat.Options (ChatOpts (..)) import Simplex.Chat.Protocol (currentChatVersion, pqEncryptionCompressionVersion, supportedChatVRange) import Simplex.Chat.Store (agentStoreFile, chatStoreFile) -import Simplex.Chat.Types (VersionRangeChat, authErrDisableCount, sameVerificationCode, verificationCode, pattern VersionChat) +import Simplex.Chat.Types (VersionRangeChat, authErrDisableCount, sameVerificationCode, verificationCode, VersionChat, pattern VersionChat) import qualified Simplex.Messaging.Crypto as C -import Simplex.Messaging.Crypto.Ratchet (PQEncryption (..), pattern PQSupportOn, pattern PQSupportOff, pattern PQEncOn, pattern PQEncOff) +import Simplex.Messaging.Crypto.Ratchet (PQEncryption (..), pattern PQEncOff, pattern PQEncOn, pattern PQSupportOff, pattern PQSupportOn) import Simplex.Messaging.Util (safeDecodeUtf8) import Simplex.Messaging.Version import System.Directory (copyFile, doesDirectoryExist, doesFileExist) @@ -131,6 +131,8 @@ chatDirectTests = do describe "PQ tests" $ do describe "enable PQ before connection, connect via invitation link" $ pqMatrix2 runTestPQConnectViaLink describe "enable PQ before connection, connect via contact address" $ pqMatrix2 runTestPQConnectViaAddress + describe "connect via invitation link with PQ encryption enabled" $ pqVersionTestMatrix2 runTestPQVersionsViaLink + describe "connect via contact address with PQ encryption enabled" $ pqVersionTestMatrix2 runTestPQVersionsViaAddress it "should enable PQ after several messages in connection without PQ" testPQEnableContact it "should enable PQ, reduce envelope size and enable compression" testPQEnableContactCompression where @@ -2821,6 +2823,79 @@ runTestPQConnectViaAddress (alice, aPQ) (bob, bPQ) = do pqSend = if pqEnabled then (+#>) else (\#>) e2eeInfo = if pqEnabled then e2eeInfoPQStr else e2eeInfoNoPQStr +runTestPQVersionsViaLink :: HasCallStack => TestCC -> TestCC -> Bool -> VersionChat -> IO () +runTestPQVersionsViaLink alice bob pqExpected vExpected = do + img <- genProfileImgForLink + let profileImage = "data:image/png;base64," <> B.unpack img + alice `send` ("/set profile image " <> profileImage) + _trimmedCmd1 <- getTermLine alice + alice <## "profile image updated" + bob `send` ("/set profile image " <> profileImage) + _trimmedCmd2 <- getTermLine bob + bob <## "profile image updated" + + pqOn alice + pqOn bob + + connectUsers alice bob + + (alice, "hi", vExpected) `pqSend` (bob, vExpected) + (bob, "hey", vExpected) `pqSend` (alice, vExpected) + + alice ##> "/_get chat @2 count=100" + ra <- chat <$> getTermLine alice + ra `shouldContain` [(0, e2eeInfo)] + alice `pqForContact` 2 `shouldReturn` PQEncryption pqExpected + + bob ##> "/_get chat @2 count=100" + rb <- chat <$> getTermLine bob + rb `shouldContain` [(0, e2eeInfo)] + bob `pqForContact` 2 `shouldReturn` PQEncryption pqExpected + where + pqSend = if pqExpected then (+:#>) else (\:#>) + e2eeInfo = if pqExpected then e2eeInfoPQStr else e2eeInfoNoPQStr + +runTestPQVersionsViaAddress :: HasCallStack => TestCC -> TestCC -> Bool -> VersionChat -> IO () +runTestPQVersionsViaAddress alice bob pqExpected vExpected = do + img <- genProfileImgForAddress + let profileImage = "data:image/png;base64," <> B.unpack img + alice `send` ("/set profile image " <> profileImage) + _trimmedCmd1 <- getTermLine alice + alice <## "profile image updated" + bob `send` ("/set profile image " <> profileImage) + _trimmedCmd2 <- getTermLine bob + bob <## "profile image updated" + + pqOn alice + pqOn bob + + alice ##> "/ad" + cLink <- getContactLink alice True + bob ##> ("/c " <> cLink) + alice <#? bob + alice @@@ [("<@bob", "")] + alice ##> "/ac bob" + alice <## "bob (Bob): accepting contact request..." + concurrently_ + (bob <## "alice (Alice): contact is connected") + (alice <## "bob (Bob): contact is connected") + + (alice, "hi", vExpected) `pqSend` (bob, vExpected) + (bob, "hey", vExpected) `pqSend` (alice, vExpected) + + alice ##> "/_get chat @2 count=100" + ra <- chat <$> getTermLine alice + ra `shouldContain` [(0, e2eeInfo)] + alice `pqForContact` 2 `shouldReturn` PQEncryption pqExpected + + bob ##> "/_get chat @2 count=100" + rb <- chat <$> getTermLine bob + rb `shouldContain` [(0, e2eeInfo)] + bob `pqForContact` 2 `shouldReturn` PQEncryption pqExpected + where + pqSend = if pqExpected then (+:#>) else (\:#>) + e2eeInfo = if pqExpected then e2eeInfoPQStr else e2eeInfoNoPQStr + testPQEnableContact :: HasCallStack => FilePath -> IO () testPQEnableContact = testChat2 aliceProfile bobProfile $ \alice bob -> do diff --git a/tests/ChatTests/Utils.hs b/tests/ChatTests/Utils.hs index 21993d11ef..16a75377fd 100644 --- a/tests/ChatTests/Utils.hs +++ b/tests/ChatTests/Utils.hs @@ -13,6 +13,7 @@ import Control.Concurrent.Async (concurrently_) import Control.Concurrent.STM import Control.Monad (unless, when) import Control.Monad.Except (runExceptT) +import Data.ByteString (ByteString) import qualified Data.ByteString.Base64 as B64 import qualified Data.ByteString.Char8 as B import Data.Char (isDigit) @@ -128,6 +129,23 @@ pqMatrix2 runTest = do where test aPQ bPQ = testChat2 aliceProfile bobProfile $ \a b -> runTest (a, aPQ) (b, bPQ) +pqVersionTestMatrix2 :: (HasCallStack => TestCC -> TestCC -> Bool -> VersionChat -> IO ()) -> SpecWith FilePath +pqVersionTestMatrix2 runTest = do + it "current" $ testChat2 aliceProfile bobProfile (runTest' True pqEncryptionCompressionVersion) + it "prev" $ testChatCfg2 testCfgVPrev aliceProfile bobProfile (runTest' False (VersionChat 6)) + it "prev to curr" $ runTestCfg2 testCfg testCfgVPrev (runTest' False (VersionChat 6)) + it "curr to prev" $ runTestCfg2 testCfgVPrev testCfg (runTest' False (VersionChat 6)) + it "old (1st supported)" $ testChatCfg2 testCfgV1 aliceProfile bobProfile (runTest' False (VersionChat 1)) + it "old to curr" $ runTestCfg2 testCfg testCfgV1 (runTest' False (VersionChat 1)) + it "curr to old" $ runTestCfg2 testCfgV1 testCfg (runTest' False (VersionChat 1)) + it "next" $ testChatCfg2 testCfgVNext aliceProfile bobProfile (runTest' True pqEncryptionCompressionVersion) + it "next to curr" $ runTestCfg2 testCfg testCfgVNext (runTest' True pqEncryptionCompressionVersion) + it "curr to next" $ runTestCfg2 testCfgVNext testCfg (runTest' True pqEncryptionCompressionVersion) + it "next to prev" $ runTestCfg2 testCfgVPrev testCfgVNext (runTest' False (VersionChat 6)) + it "prev to next" $ runTestCfg2 testCfgVNext testCfgVPrev (runTest' False (VersionChat 6)) + where + runTest' pqExpected v a b = runTest a b pqExpected v + withTestChatGroup3Connected :: HasCallStack => FilePath -> String -> (HasCallStack => TestCC -> IO a) -> IO a withTestChatGroup3Connected tmp dbPrefix action = do withTestChat tmp dbPrefix $ \cc -> do @@ -236,7 +254,21 @@ sndRcvImg pqEnc enabled (cc1, msg, v1) (cc2, v2) = do cc2 `pqRcvForContact` 2 `shouldReturn` pqEnc cc2 `pqVerForContact` 2 `shouldReturn` v2 where - lrgLen = maxEncodedMsgLength PQSupportOff * 3 `div` 4 - 98 -- this is max size for binary image preview given the rest of the message + lrgLen = maxEncodedMsgLength PQSupportOff * 3 `div` 4 - 110 -- 98 is ~ max size for binary image preview given the rest of the message + +genProfileImgForLink :: IO ByteString +genProfileImgForLink = do + g <- C.newRandom + atomically $ B64.encode <$> C.randomBytes lrgLen g + where + lrgLen = maxConnInfoLength PQSupportOff * 3 `div` 4 - 240 -- 214 is the magic number to make tests pass (10737) + +genProfileImgForAddress :: IO ByteString +genProfileImgForAddress = do + g <- C.newRandom + atomically $ B64.encode <$> C.randomBytes lrgLen g + where + lrgLen = maxConnInfoLength PQSupportOff * 3 `div` 4 - 260 -- 238 is the magic number to make tests pass (10713) -- PQ combinators /