mirror of
https://github.com/simplex-chat/simplexmq.git
synced 2026-10-23 13:02:28 +00:00
agent: quantum-resistant double ratchet encryption (#939)
* doc * diff * ratchet header * types * ratchet step with PQ KEM, message header with KEM * comment * update types, remove Eq instances, store KEM keys to database * pqx3dh * PQ double ratchet test * pqdr tests pass * fix most tests * refactor * allow KEM proposals from both sides * test names * agent API parameters to use PQ KEM * initialize ratchet state for enabling KEM * fix/test KEM state machine to support disabling/enabling via messages * more tests * diff * diff2 * refactor * refactor * refactor * refactor * remove Maybe * rename * add PQ encryption status to CON, MID and MSG events and sendMessage API results * different PQ parameter when creating connection * rename/reorganize types for PQ encryption modes * rename * fix testWaitDeliveryTimeout * rename * rename2 * ghc8107 * rename * increase timeouts for concurrent send/receive test * enable all tests --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
This commit is contained in:
1 parent
30fd4065d9
commit
e06e22328f
31 files changed
+1776
-689
No files matched your search
+165
-90
@@ -4,6 +4,7 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE PatternSynonyms #-}
|
||||
{-# LANGUAGE PostfixOperators #-}
|
||||
{-# LANGUAGE RankNTypes #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# LANGUAGE TypeApplications #-}
|
||||
{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-}
|
||||
@@ -12,12 +13,12 @@ module AgentTests (agentTests) where
|
||||
|
||||
import AgentTests.ConnectionRequestTests
|
||||
import AgentTests.DoubleRatchetTests (doubleRatchetTests)
|
||||
import AgentTests.FunctionalAPITests (functionalAPITests)
|
||||
import AgentTests.FunctionalAPITests (functionalAPITests, pattern Msg, pattern Msg')
|
||||
import AgentTests.MigrationTests (migrationTests)
|
||||
import AgentTests.NotificationTests (notificationTests)
|
||||
import AgentTests.SQLiteTests (storeTests)
|
||||
import Control.Concurrent
|
||||
import Control.Monad (forM_)
|
||||
import Control.Monad (forM_, when)
|
||||
import Data.ByteString.Char8 (ByteString)
|
||||
import qualified Data.ByteString.Char8 as B
|
||||
import Data.Maybe (fromJust)
|
||||
@@ -26,15 +27,18 @@ import GHC.Stack (withFrozenCallStack)
|
||||
import Network.HTTP.Types (urlEncode)
|
||||
import SMPAgentClient
|
||||
import SMPClient (testKeyHash, testPort, testPort2, testStoreLogFile, withSmpServer, withSmpServerStoreLogOn)
|
||||
import Simplex.Messaging.Agent.Protocol
|
||||
import Simplex.Messaging.Agent.Protocol hiding (MID)
|
||||
import qualified Simplex.Messaging.Agent.Protocol as A
|
||||
import Simplex.Messaging.Crypto.Ratchet (InitialKeys (..), PQEncryption (..), pattern PQEncOn, pattern PQEncOff)
|
||||
import qualified Simplex.Messaging.Crypto.Ratchet as CR
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Protocol (ErrorType (..), MsgBody)
|
||||
import Simplex.Messaging.Protocol (ErrorType (..))
|
||||
import Simplex.Messaging.Transport (ATransport (..), TProxy (..), Transport (..))
|
||||
import Simplex.Messaging.Util (bshow)
|
||||
import System.Directory (removeFile)
|
||||
import System.Timeout
|
||||
import Test.Hspec
|
||||
import Util
|
||||
|
||||
agentTests :: ATransport -> Spec
|
||||
agentTests (ATransport t) = do
|
||||
@@ -46,24 +50,25 @@ agentTests (ATransport t) = do
|
||||
describe "Migration tests" migrationTests
|
||||
describe "SMP agent protocol syntax" $ syntaxTests t
|
||||
describe "Establishing duplex connection (via agent protocol)" $ do
|
||||
-- These tests are disabled because the agent does not work correctly with multiple connected TCP clients
|
||||
xit "should connect via one server and one agent" $ do
|
||||
smpAgentTest2_1_1 $ testDuplexConnection t
|
||||
xit "should connect via one server and one agent (random IDs)" $ do
|
||||
smpAgentTest2_1_1 $ testDuplexConnRandomIds t
|
||||
skip "These tests are disabled because the agent does not work correctly with multiple connected TCP clients" $
|
||||
describe "one agent" $ do
|
||||
it "should connect via one server and one agent" $ do
|
||||
smpAgentTest2_1_1 $ testDuplexConnection t
|
||||
it "should connect via one server and one agent (random IDs)" $ do
|
||||
smpAgentTest2_1_1 $ testDuplexConnRandomIds t
|
||||
it "should connect via one server and 2 agents" $ do
|
||||
smpAgentTest2_2_1 $ testDuplexConnection t
|
||||
it "should connect via one server and 2 agents (random IDs)" $ do
|
||||
smpAgentTest2_2_1 $ testDuplexConnRandomIds t
|
||||
it "should connect via 2 servers and 2 agents" $ do
|
||||
smpAgentTest2_2_2 $ testDuplexConnection t
|
||||
it "should connect via 2 servers and 2 agents (random IDs)" $ do
|
||||
smpAgentTest2_2_2 $ testDuplexConnRandomIds t
|
||||
describe "should connect via 2 servers and 2 agents" $ do
|
||||
pqMatrix2 t smpAgentTest2_2_2 testDuplexConnection'
|
||||
describe "should connect via 2 servers and 2 agents (random IDs)" $ do
|
||||
pqMatrix2 t smpAgentTest2_2_2 testDuplexConnRandomIds'
|
||||
describe "Establishing connections via `contact connection`" $ do
|
||||
it "should connect via contact connection with one server and 3 agents" $ do
|
||||
smpAgentTest3 $ testContactConnection t
|
||||
it "should connect via contact connection with one server and 2 agents (random IDs)" $ do
|
||||
smpAgentTest2_2_1 $ testContactConnRandomIds t
|
||||
describe "should connect via contact connection with one server and 3 agents" $ do
|
||||
pqMatrix3 t smpAgentTest3 testContactConnection
|
||||
describe "should connect via contact connection with one server and 2 agents (random IDs)" $ do
|
||||
pqMatrix2NoInv t smpAgentTest2_2_1 testContactConnRandomIds
|
||||
it "should support rejecting contact request" $ do
|
||||
smpAgentTest2_2_1 $ testRejectContactRequest t
|
||||
describe "Connection subscriptions" $ do
|
||||
@@ -72,8 +77,8 @@ agentTests (ATransport t) = do
|
||||
it "should send notifications to client when server disconnects" $ do
|
||||
smpAgentServerTest $ testSubscrNotification t
|
||||
describe "Message delivery and server reconnection" $ do
|
||||
it "should deliver messages after losing server connection and re-connecting" $ do
|
||||
smpAgentTest2_2_2_needs_server $ testMsgDeliveryServerRestart t
|
||||
describe "should deliver messages after losing server connection and re-connecting" $
|
||||
pqMatrix2 t smpAgentTest2_2_2_needs_server testMsgDeliveryServerRestart
|
||||
it "should connect to the server when server goes up if it initially was down" $ do
|
||||
smpAgentTestN [] $ testServerConnectionAfterError t
|
||||
it "should deliver pending messages after agent restarting" $ do
|
||||
@@ -133,6 +138,9 @@ action #> (corrId, connId, cmd) = withFrozenCallStack $ action `shouldReturn` (c
|
||||
(=#>) :: IO (AEntityTransmissionOrError 'Agent 'AEConn) -> (AEntityTransmission 'Agent 'AEConn -> Bool) -> Expectation
|
||||
action =#> p = withFrozenCallStack $ action >>= (`shouldSatisfy` p . correctTransmission)
|
||||
|
||||
pattern MID :: AgentMsgId -> ACommand 'Agent 'AEConn
|
||||
pattern MID msgId = A.MID msgId PQEncOn
|
||||
|
||||
correctTransmission :: (ACorrId, ConnId, Either AgentErrorType cmd) -> (ACorrId, ConnId, cmd)
|
||||
correctTransmission (corrId, connId, cmdOrErr) = case cmdOrErr of
|
||||
Right cmd -> (corrId, connId, cmd)
|
||||
@@ -161,130 +169,175 @@ h #:# err = tryGet `shouldReturn` ()
|
||||
Just _ -> error err
|
||||
_ -> return ()
|
||||
|
||||
pattern Msg :: MsgBody -> ACommand 'Agent e
|
||||
pattern Msg msgBody <- MSG MsgMeta {integrity = MsgOk} _ msgBody
|
||||
type PQMatrix2 c =
|
||||
HasCallStack =>
|
||||
TProxy c ->
|
||||
(HasCallStack => (c -> c -> IO ()) -> Expectation) ->
|
||||
(HasCallStack => (c, InitialKeys) -> (c, PQEncryption) -> IO ()) ->
|
||||
Spec
|
||||
|
||||
pattern Msg' :: AgentMsgId -> MsgBody -> ACommand 'Agent e
|
||||
pattern Msg' aMsgId msgBody <- MSG MsgMeta {integrity = MsgOk, recipient = (aMsgId, _)} _ msgBody
|
||||
pqMatrix2 :: PQMatrix2 c
|
||||
pqMatrix2 = pqMatrix2_ True
|
||||
|
||||
pqMatrix2NoInv :: PQMatrix2 c
|
||||
pqMatrix2NoInv = pqMatrix2_ False
|
||||
|
||||
pqMatrix2_ :: Bool -> PQMatrix2 c
|
||||
pqMatrix2_ pqInv _ smpTest test = do
|
||||
it "dh/dh handshake" $ smpTest $ \a b -> test (a, ikPQOff) (b, PQEncOff)
|
||||
it "dh/pq handshake" $ smpTest $ \a b -> test (a, ikPQOff) (b, PQEncOn)
|
||||
it "pq/dh handshake" $ smpTest $ \a b -> test (a, ikPQOn) (b, PQEncOff)
|
||||
it "pq/pq handshake" $ smpTest $ \a b -> test (a, ikPQOn) (b, PQEncOn)
|
||||
when pqInv $ do
|
||||
it "pq-inv/dh handshake" $ smpTest $ \a b -> test (a, IKUsePQ) (b, PQEncOff)
|
||||
it "pq-inv/pq handshake" $ smpTest $ \a b -> test (a, IKUsePQ) (b, PQEncOn)
|
||||
|
||||
pqMatrix3 ::
|
||||
HasCallStack =>
|
||||
TProxy c ->
|
||||
(HasCallStack => (c -> c -> c -> IO ()) -> Expectation) ->
|
||||
(HasCallStack => (c, InitialKeys) -> (c, PQEncryption) -> (c, PQEncryption) -> IO ()) ->
|
||||
Spec
|
||||
pqMatrix3 _ smpTest test = do
|
||||
it "dh" $ smpTest $ \a b c -> test (a, ikPQOff) (b, PQEncOff) (c, PQEncOff)
|
||||
it "dh/dh/pq" $ smpTest $ \a b c -> test (a, ikPQOff) (b, PQEncOff) (c, PQEncOn)
|
||||
it "dh/pq/dh" $ smpTest $ \a b c -> test (a, ikPQOff) (b, PQEncOn) (c, PQEncOff)
|
||||
it "dh/pq/pq" $ smpTest $ \a b c -> test (a, ikPQOff) (b, PQEncOn) (c, PQEncOn)
|
||||
it "pq/dh/dh" $ smpTest $ \a b c -> test (a, ikPQOn) (b, PQEncOff) (c, PQEncOff)
|
||||
it "pq/dh/pq" $ smpTest $ \a b c -> test (a, ikPQOn) (b, PQEncOff) (c, PQEncOn)
|
||||
it "pq/pq/dh" $ smpTest $ \a b c -> test (a, ikPQOn) (b, PQEncOn) (c, PQEncOff)
|
||||
it "pq" $ smpTest $ \a b c -> test (a, ikPQOn) (b, PQEncOn) (c, PQEncOn)
|
||||
|
||||
testDuplexConnection :: (HasCallStack, Transport c) => TProxy c -> c -> c -> IO ()
|
||||
testDuplexConnection _ alice bob = do
|
||||
("1", "bob", Right (INV cReq)) <- alice #: ("1", "bob", "NEW T INV subscribe")
|
||||
testDuplexConnection _ alice bob = testDuplexConnection' (alice, ikPQOn) (bob, PQEncOn)
|
||||
|
||||
testDuplexConnection' :: (HasCallStack, Transport c) => (c, InitialKeys) -> (c, PQEncryption) -> IO ()
|
||||
testDuplexConnection' (alice, aPQ) (bob, bPQ) = do
|
||||
let pq = pqConnectionMode aPQ bPQ
|
||||
("1", "bob", Right (INV cReq)) <- alice #: ("1", "bob", "NEW T INV" <> pqConnModeStr aPQ <> " subscribe")
|
||||
let cReq' = strEncode cReq
|
||||
bob #: ("11", "alice", "JOIN T " <> cReq' <> " subscribe 14\nbob's connInfo") #> ("11", "alice", OK)
|
||||
bob #: ("11", "alice", "JOIN T " <> cReq' <> enableKEMStr bPQ <> " subscribe 14\nbob's connInfo") #> ("11", "alice", OK)
|
||||
("", "bob", Right (CONF confId _ "bob's connInfo")) <- (alice <#:)
|
||||
alice #: ("2", "bob", "LET " <> confId <> " 16\nalice's connInfo") #> ("2", "bob", OK)
|
||||
bob <# ("", "alice", INFO "alice's connInfo")
|
||||
bob <# ("", "alice", CON)
|
||||
alice <# ("", "bob", CON)
|
||||
bob <# ("", "alice", CON pq)
|
||||
alice <# ("", "bob", CON pq)
|
||||
-- message IDs 1 to 3 get assigned to control messages, so first MSG is assigned ID 4
|
||||
alice #: ("3", "bob", "SEND F :hello") #> ("3", "bob", MID 4)
|
||||
alice #: ("3", "bob", "SEND F :hello") #> ("3", "bob", A.MID 4 pq)
|
||||
alice <# ("", "bob", SENT 4)
|
||||
bob <#= \case ("", "alice", Msg' 4 "hello") -> True; _ -> False
|
||||
bob <#= \case ("", "alice", Msg' 4 pq' "hello") -> pq == pq'; _ -> False
|
||||
bob #: ("12", "alice", "ACK 4") #> ("12", "alice", OK)
|
||||
alice #: ("4", "bob", "SEND F :how are you?") #> ("4", "bob", MID 5)
|
||||
alice #: ("4", "bob", "SEND F :how are you?") #> ("4", "bob", A.MID 5 pq)
|
||||
alice <# ("", "bob", SENT 5)
|
||||
bob <#= \case ("", "alice", Msg' 5 "how are you?") -> True; _ -> False
|
||||
bob <#= \case ("", "alice", Msg' 5 pq' "how are you?") -> pq == pq'; _ -> False
|
||||
bob #: ("13", "alice", "ACK 5") #> ("13", "alice", OK)
|
||||
bob #: ("14", "alice", "SEND F 9\nhello too") #> ("14", "alice", MID 6)
|
||||
bob #: ("14", "alice", "SEND F 9\nhello too") #> ("14", "alice", A.MID 6 pq)
|
||||
bob <# ("", "alice", SENT 6)
|
||||
alice <#= \case ("", "bob", Msg' 6 "hello too") -> True; _ -> False
|
||||
alice <#= \case ("", "bob", Msg' 6 pq' "hello too") -> pq == pq'; _ -> False
|
||||
alice #: ("3a", "bob", "ACK 6") #> ("3a", "bob", OK)
|
||||
bob #: ("15", "alice", "SEND F 9\nmessage 1") #> ("15", "alice", MID 7)
|
||||
bob #: ("15", "alice", "SEND F 9\nmessage 1") #> ("15", "alice", A.MID 7 pq)
|
||||
bob <# ("", "alice", SENT 7)
|
||||
alice <#= \case ("", "bob", Msg' 7 "message 1") -> True; _ -> False
|
||||
alice <#= \case ("", "bob", Msg' 7 pq' "message 1") -> pq == pq'; _ -> False
|
||||
alice #: ("4a", "bob", "ACK 7") #> ("4a", "bob", OK)
|
||||
alice #: ("5", "bob", "OFF") #> ("5", "bob", OK)
|
||||
bob #: ("17", "alice", "SEND F 9\nmessage 3") #> ("17", "alice", MID 8)
|
||||
bob #: ("17", "alice", "SEND F 9\nmessage 3") #> ("17", "alice", A.MID 8 pq)
|
||||
bob <# ("", "alice", MERR 8 (SMP AUTH))
|
||||
alice #: ("6", "bob", "DEL") #> ("6", "bob", OK)
|
||||
alice #:# "nothing else should be delivered to alice"
|
||||
|
||||
testDuplexConnRandomIds :: Transport c => TProxy c -> c -> c -> IO ()
|
||||
testDuplexConnRandomIds _ alice bob = do
|
||||
("1", bobConn, Right (INV cReq)) <- alice #: ("1", "", "NEW T INV subscribe")
|
||||
testDuplexConnRandomIds :: (HasCallStack, Transport c) => TProxy c -> c -> c -> IO ()
|
||||
testDuplexConnRandomIds _ alice bob = testDuplexConnRandomIds' (alice, ikPQOn) (bob, PQEncOn)
|
||||
|
||||
testDuplexConnRandomIds' :: (HasCallStack, Transport c) => (c, InitialKeys) -> (c, PQEncryption) -> IO ()
|
||||
testDuplexConnRandomIds' (alice, aPQ) (bob, bPQ) = do
|
||||
let pq = pqConnectionMode aPQ bPQ
|
||||
("1", bobConn, Right (INV cReq)) <- alice #: ("1", "", "NEW T INV" <> pqConnModeStr aPQ <> " subscribe")
|
||||
let cReq' = strEncode cReq
|
||||
("11", aliceConn, Right OK) <- bob #: ("11", "", "JOIN T " <> cReq' <> " subscribe 14\nbob's connInfo")
|
||||
("11", aliceConn, Right OK) <- bob #: ("11", "", "JOIN T " <> cReq' <> enableKEMStr bPQ <> " subscribe 14\nbob's connInfo")
|
||||
("", bobConn', Right (CONF confId _ "bob's connInfo")) <- (alice <#:)
|
||||
bobConn' `shouldBe` bobConn
|
||||
alice #: ("2", bobConn, "LET " <> confId <> " 16\nalice's connInfo") =#> \case ("2", c, OK) -> c == bobConn; _ -> False
|
||||
bob <# ("", aliceConn, INFO "alice's connInfo")
|
||||
bob <# ("", aliceConn, CON)
|
||||
alice <# ("", bobConn, CON)
|
||||
alice #: ("2", bobConn, "SEND F :hello") #> ("2", bobConn, MID 4)
|
||||
bob <# ("", aliceConn, CON pq)
|
||||
alice <# ("", bobConn, CON pq)
|
||||
alice #: ("2", bobConn, "SEND F :hello") #> ("2", bobConn, A.MID 4 pq)
|
||||
alice <# ("", bobConn, SENT 4)
|
||||
bob <#= \case ("", c, Msg "hello") -> c == aliceConn; _ -> False
|
||||
bob <#= \case ("", c, Msg' 4 pq' "hello") -> c == aliceConn && pq == pq'; _ -> False
|
||||
bob #: ("12", aliceConn, "ACK 4") #> ("12", aliceConn, OK)
|
||||
alice #: ("3", bobConn, "SEND F :how are you?") #> ("3", bobConn, MID 5)
|
||||
alice #: ("3", bobConn, "SEND F :how are you?") #> ("3", bobConn, A.MID 5 pq)
|
||||
alice <# ("", bobConn, SENT 5)
|
||||
bob <#= \case ("", c, Msg "how are you?") -> c == aliceConn; _ -> False
|
||||
bob <#= \case ("", c, Msg' 5 pq' "how are you?") -> c == aliceConn && pq == pq'; _ -> False
|
||||
bob #: ("13", aliceConn, "ACK 5") #> ("13", aliceConn, OK)
|
||||
bob #: ("14", aliceConn, "SEND F 9\nhello too") #> ("14", aliceConn, MID 6)
|
||||
bob #: ("14", aliceConn, "SEND F 9\nhello too") #> ("14", aliceConn, A.MID 6 pq)
|
||||
bob <# ("", aliceConn, SENT 6)
|
||||
alice <#= \case ("", c, Msg "hello too") -> c == bobConn; _ -> False
|
||||
alice <#= \case ("", c, Msg' 6 pq' "hello too") -> c == bobConn && pq == pq'; _ -> False
|
||||
alice #: ("3a", bobConn, "ACK 6") #> ("3a", bobConn, OK)
|
||||
bob #: ("15", aliceConn, "SEND F 9\nmessage 1") #> ("15", aliceConn, MID 7)
|
||||
bob #: ("15", aliceConn, "SEND F 9\nmessage 1") #> ("15", aliceConn, A.MID 7 pq)
|
||||
bob <# ("", aliceConn, SENT 7)
|
||||
alice <#= \case ("", c, Msg "message 1") -> c == bobConn; _ -> False
|
||||
alice <#= \case ("", c, Msg' 7 pq' "message 1") -> c == bobConn && pq == pq'; _ -> False
|
||||
alice #: ("4a", bobConn, "ACK 7") #> ("4a", bobConn, OK)
|
||||
alice #: ("5", bobConn, "OFF") #> ("5", bobConn, OK)
|
||||
bob #: ("17", aliceConn, "SEND F 9\nmessage 3") #> ("17", aliceConn, MID 8)
|
||||
bob #: ("17", aliceConn, "SEND F 9\nmessage 3") #> ("17", aliceConn, A.MID 8 pq)
|
||||
bob <# ("", aliceConn, MERR 8 (SMP AUTH))
|
||||
alice #: ("6", bobConn, "DEL") #> ("6", bobConn, OK)
|
||||
alice #:# "nothing else should be delivered to alice"
|
||||
|
||||
testContactConnection :: Transport c => TProxy c -> c -> c -> c -> IO ()
|
||||
testContactConnection _ alice bob tom = do
|
||||
("1", "alice_contact", Right (INV cReq)) <- alice #: ("1", "alice_contact", "NEW T CON subscribe")
|
||||
testContactConnection :: Transport c => (c, InitialKeys) -> (c, PQEncryption) -> (c, PQEncryption) -> IO ()
|
||||
testContactConnection (alice, aPQ) (bob, bPQ) (tom, tPQ) = do
|
||||
("1", "alice_contact", Right (INV cReq)) <- alice #: ("1", "alice_contact", "NEW T CON" <> pqConnModeStr aPQ <> " subscribe")
|
||||
let cReq' = strEncode cReq
|
||||
abPQ = pqConnectionMode aPQ bPQ
|
||||
aPQMode = CR.connPQEncryption aPQ
|
||||
|
||||
bob #: ("11", "alice", "JOIN T " <> cReq' <> " subscribe 14\nbob's connInfo") #> ("11", "alice", OK)
|
||||
bob #: ("11", "alice", "JOIN T " <> cReq' <> enableKEMStr bPQ <> " subscribe 14\nbob's connInfo") #> ("11", "alice", OK)
|
||||
("", "alice_contact", Right (REQ aInvId _ "bob's connInfo")) <- (alice <#:)
|
||||
alice #: ("2", "bob", "ACPT " <> aInvId <> " 16\nalice's connInfo") #> ("2", "bob", OK)
|
||||
alice #: ("2", "bob", "ACPT " <> aInvId <> enableKEMStr aPQMode <> " 16\nalice's connInfo") #> ("2", "bob", OK)
|
||||
("", "alice", Right (CONF bConfId _ "alice's connInfo")) <- (bob <#:)
|
||||
bob #: ("12", "alice", "LET " <> bConfId <> " 16\nbob's connInfo 2") #> ("12", "alice", OK)
|
||||
alice <# ("", "bob", INFO "bob's connInfo 2")
|
||||
alice <# ("", "bob", CON)
|
||||
bob <# ("", "alice", CON)
|
||||
alice #: ("3", "bob", "SEND F :hi") #> ("3", "bob", MID 4)
|
||||
alice <# ("", "bob", CON abPQ)
|
||||
bob <# ("", "alice", CON abPQ)
|
||||
alice #: ("3", "bob", "SEND F :hi") #> ("3", "bob", A.MID 4 abPQ)
|
||||
alice <# ("", "bob", SENT 4)
|
||||
bob <#= \case ("", "alice", Msg "hi") -> True; _ -> False
|
||||
bob <#= \case ("", "alice", Msg' 4 pq' "hi") -> pq' == abPQ; _ -> False
|
||||
bob #: ("13", "alice", "ACK 4") #> ("13", "alice", OK)
|
||||
|
||||
tom #: ("21", "alice", "JOIN T " <> cReq' <> " subscribe 14\ntom's connInfo") #> ("21", "alice", OK)
|
||||
let atPQ = pqConnectionMode aPQ tPQ
|
||||
tom #: ("21", "alice", "JOIN T " <> cReq' <> enableKEMStr tPQ <> " subscribe 14\ntom's connInfo") #> ("21", "alice", OK)
|
||||
("", "alice_contact", Right (REQ aInvId' _ "tom's connInfo")) <- (alice <#:)
|
||||
alice #: ("4", "tom", "ACPT " <> aInvId' <> " 16\nalice's connInfo") #> ("4", "tom", OK)
|
||||
alice #: ("4", "tom", "ACPT " <> aInvId' <> enableKEMStr aPQMode <> " 16\nalice's connInfo") #> ("4", "tom", OK)
|
||||
("", "alice", Right (CONF tConfId _ "alice's connInfo")) <- (tom <#:)
|
||||
tom #: ("22", "alice", "LET " <> tConfId <> " 16\ntom's connInfo 2") #> ("22", "alice", OK)
|
||||
alice <# ("", "tom", INFO "tom's connInfo 2")
|
||||
alice <# ("", "tom", CON)
|
||||
tom <# ("", "alice", CON)
|
||||
alice #: ("5", "tom", "SEND F :hi there") #> ("5", "tom", MID 4)
|
||||
alice <# ("", "tom", CON atPQ)
|
||||
tom <# ("", "alice", CON atPQ)
|
||||
alice #: ("5", "tom", "SEND F :hi there") #> ("5", "tom", A.MID 4 atPQ)
|
||||
alice <# ("", "tom", SENT 4)
|
||||
tom <#= \case ("", "alice", Msg "hi there") -> True; _ -> False
|
||||
tom <#= \case ("", "alice", Msg' 4 pq' "hi there") -> pq' == atPQ; _ -> False
|
||||
tom #: ("23", "alice", "ACK 4") #> ("23", "alice", OK)
|
||||
|
||||
testContactConnRandomIds :: Transport c => TProxy c -> c -> c -> IO ()
|
||||
testContactConnRandomIds _ alice bob = do
|
||||
("1", aliceContact, Right (INV cReq)) <- alice #: ("1", "", "NEW T CON subscribe")
|
||||
testContactConnRandomIds :: Transport c => (c, InitialKeys) -> (c, PQEncryption) -> IO ()
|
||||
testContactConnRandomIds (alice, aPQ) (bob, bPQ) = do
|
||||
let pq = pqConnectionMode aPQ bPQ
|
||||
("1", aliceContact, Right (INV cReq)) <- alice #: ("1", "", "NEW T CON" <> pqConnModeStr aPQ <> " subscribe")
|
||||
let cReq' = strEncode cReq
|
||||
|
||||
("11", aliceConn, Right OK) <- bob #: ("11", "", "JOIN T " <> cReq' <> " subscribe 14\nbob's connInfo")
|
||||
("11", aliceConn, Right OK) <- bob #: ("11", "", "JOIN T " <> cReq' <> enableKEMStr bPQ <> " subscribe 14\nbob's connInfo")
|
||||
("", aliceContact', Right (REQ aInvId _ "bob's connInfo")) <- (alice <#:)
|
||||
aliceContact' `shouldBe` aliceContact
|
||||
|
||||
("2", bobConn, Right OK) <- alice #: ("2", "", "ACPT " <> aInvId <> " 16\nalice's connInfo")
|
||||
("2", bobConn, Right OK) <- alice #: ("2", "", "ACPT " <> aInvId <> enableKEMStr (CR.connPQEncryption aPQ) <> " 16\nalice's connInfo")
|
||||
("", aliceConn', Right (CONF bConfId _ "alice's connInfo")) <- (bob <#:)
|
||||
aliceConn' `shouldBe` aliceConn
|
||||
|
||||
bob #: ("12", aliceConn, "LET " <> bConfId <> " 16\nbob's connInfo 2") #> ("12", aliceConn, OK)
|
||||
alice <# ("", bobConn, INFO "bob's connInfo 2")
|
||||
alice <# ("", bobConn, CON)
|
||||
bob <# ("", aliceConn, CON)
|
||||
alice <# ("", bobConn, CON pq)
|
||||
bob <# ("", aliceConn, CON pq)
|
||||
|
||||
alice #: ("3", bobConn, "SEND F :hi") #> ("3", bobConn, MID 4)
|
||||
alice #: ("3", bobConn, "SEND F :hi") #> ("3", bobConn, A.MID 4 pq)
|
||||
alice <# ("", bobConn, SENT 4)
|
||||
bob <#= \case ("", c, Msg "hi") -> c == aliceConn; _ -> False
|
||||
bob <#= \case ("", c, Msg' 4 pq' "hi") -> c == aliceConn && pq == pq'; _ -> False
|
||||
bob #: ("13", aliceConn, "ACK 4") #> ("13", aliceConn, OK)
|
||||
|
||||
testRejectContactRequest :: Transport c => TProxy c -> c -> c -> IO ()
|
||||
@@ -327,31 +380,32 @@ testSubscrNotification t (server, _) client = do
|
||||
withSmpServer (ATransport t) $
|
||||
client <# ("", "conn1", ERR (SMP AUTH)) -- this new server does not have the queue
|
||||
|
||||
testMsgDeliveryServerRestart :: Transport c => TProxy c -> c -> c -> IO ()
|
||||
testMsgDeliveryServerRestart t alice bob = do
|
||||
testMsgDeliveryServerRestart :: forall c. Transport c => (c, InitialKeys) -> (c, PQEncryption) -> IO ()
|
||||
testMsgDeliveryServerRestart (alice, aPQ) (bob, bPQ) = do
|
||||
let pq = pqConnectionMode aPQ bPQ
|
||||
withServer $ do
|
||||
connect (alice, "alice") (bob, "bob")
|
||||
bob #: ("1", "alice", "SEND F 2\nhi") #> ("1", "alice", MID 4)
|
||||
connect' (alice, "alice", aPQ) (bob, "bob", bPQ)
|
||||
bob #: ("1", "alice", "SEND F 2\nhi") #> ("1", "alice", A.MID 4 pq)
|
||||
bob <# ("", "alice", SENT 4)
|
||||
alice <#= \case ("", "bob", Msg "hi") -> True; _ -> False
|
||||
alice <#= \case ("", "bob", Msg' _ pq' "hi") -> pq == pq'; _ -> False
|
||||
alice #: ("11", "bob", "ACK 4") #> ("11", "bob", OK)
|
||||
alice #:# "nothing else delivered before the server is killed"
|
||||
|
||||
let server = SMPServer "localhost" testPort2 testKeyHash
|
||||
alice <#. ("", "", DOWN server ["bob"])
|
||||
bob #: ("2", "alice", "SEND F 11\nhello again") #> ("2", "alice", MID 5)
|
||||
bob #: ("2", "alice", "SEND F 11\nhello again") #> ("2", "alice", A.MID 5 pq)
|
||||
bob #:# "nothing else delivered before the server is restarted"
|
||||
alice #:# "nothing else delivered before the server is restarted"
|
||||
|
||||
withServer $ do
|
||||
bob <# ("", "alice", SENT 5)
|
||||
alice <#. ("", "", UP server ["bob"])
|
||||
alice <#= \case ("", "bob", Msg "hello again") -> True; _ -> False
|
||||
alice <#= \case ("", "bob", Msg' _ pq' "hello again") -> pq == pq'; _ -> False
|
||||
alice #: ("12", "bob", "ACK 5") #> ("12", "bob", OK)
|
||||
|
||||
removeFile testStoreLogFile
|
||||
where
|
||||
withServer test' = withSmpServerStoreLogOn (ATransport t) testPort2 (const test') `shouldReturn` ()
|
||||
withServer test' = withSmpServerStoreLogOn (transport @c) testPort2 (const test') `shouldReturn` ()
|
||||
|
||||
testServerConnectionAfterError :: forall c. Transport c => TProxy c -> [c] -> IO ()
|
||||
testServerConnectionAfterError t _ = do
|
||||
@@ -492,16 +546,37 @@ testResumeDeliveryQuotaExceeded _ alice bob = do
|
||||
-- message 8 is skipped because of alice agent sending "QCONT" message
|
||||
bob #: ("5", "alice", "ACK 9") #> ("5", "alice", OK)
|
||||
|
||||
connect :: forall c. Transport c => (c, ByteString) -> (c, ByteString) -> IO ()
|
||||
connect (h1, name1) (h2, name2) = do
|
||||
("c1", _, Right (INV cReq)) <- h1 #: ("c1", name2, "NEW T INV subscribe")
|
||||
ikPQOn :: InitialKeys
|
||||
ikPQOn = IKNoPQ PQEncOn
|
||||
|
||||
ikPQOff :: InitialKeys
|
||||
ikPQOff = IKNoPQ PQEncOff
|
||||
|
||||
connect :: Transport c => (c, ByteString) -> (c, ByteString) -> IO ()
|
||||
connect (h1, name1) (h2, name2) = connect' (h1, name1, ikPQOn) (h2, name2, PQEncOn)
|
||||
|
||||
connect' :: forall c. Transport c => (c, ByteString, InitialKeys) -> (c, ByteString, PQEncryption) -> IO ()
|
||||
connect' (h1, name1, pqMode1) (h2, name2, pqMode2) = do
|
||||
("c1", _, Right (INV cReq)) <- h1 #: ("c1", name2, "NEW T INV" <> pqConnModeStr pqMode1 <> " subscribe")
|
||||
let cReq' = strEncode cReq
|
||||
h2 #: ("c2", name1, "JOIN T " <> cReq' <> " subscribe 5\ninfo2") #> ("c2", name1, OK)
|
||||
h2 #: ("c2", name1, "JOIN T " <> cReq' <> enableKEMStr pqMode2 <> " subscribe 5\ninfo2") #> ("c2", name1, OK)
|
||||
("", _, Right (CONF connId _ "info2")) <- (h1 <#:)
|
||||
h1 #: ("c3", name2, "LET " <> connId <> " 5\ninfo1") #> ("c3", name2, OK)
|
||||
h2 <# ("", name1, INFO "info1")
|
||||
h2 <# ("", name1, CON)
|
||||
h1 <# ("", name2, CON)
|
||||
let pq = pqConnectionMode pqMode1 pqMode2
|
||||
h2 <# ("", name1, CON pq)
|
||||
h1 <# ("", name2, CON pq)
|
||||
|
||||
pqConnectionMode :: InitialKeys -> PQEncryption -> PQEncryption
|
||||
pqConnectionMode pqMode1 pqMode2 = PQEncryption $ enablePQ (CR.connPQEncryption pqMode1) && enablePQ pqMode2
|
||||
|
||||
enableKEMStr :: PQEncryption -> ByteString
|
||||
enableKEMStr PQEncOn = " " <> strEncode PQEncOn
|
||||
enableKEMStr _ = ""
|
||||
|
||||
pqConnModeStr :: InitialKeys -> ByteString
|
||||
pqConnModeStr (IKNoPQ PQEncOff) = ""
|
||||
pqConnModeStr pq = " " <> strEncode pq
|
||||
|
||||
sendMessage :: Transport c => (c, ConnId) -> (c, ConnId) -> ByteString -> IO ()
|
||||
sendMessage (h1, name1) (h2, name2) msg = do
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
{-# LANGUAGE DataKinds #-}
|
||||
{-# LANGUAGE DuplicateRecordFields #-}
|
||||
{-# LANGUAGE GADTs #-}
|
||||
{-# LANGUAGE OverloadedLists #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE StandaloneDeriving #-}
|
||||
{-# OPTIONS_GHC -Wno-orphans #-}
|
||||
{-# OPTIONS_GHC -fno-warn-ambiguous-fields #-}
|
||||
|
||||
module AgentTests.ConnectionRequestTests where
|
||||
|
||||
import Data.ByteString (ByteString)
|
||||
import Data.Type.Equality
|
||||
import Network.HTTP.Types (urlEncode)
|
||||
import Simplex.Messaging.Agent.Protocol
|
||||
import qualified Simplex.Messaging.Crypto as C
|
||||
@@ -17,6 +21,17 @@ import Simplex.Messaging.ServiceScheme (ServiceScheme (..))
|
||||
import Simplex.Messaging.Version
|
||||
import Test.Hspec
|
||||
|
||||
deriving instance Eq (ConnectionRequestUri m)
|
||||
|
||||
deriving instance Eq (E2ERatchetParamsUri s a)
|
||||
|
||||
deriving instance Eq (RKEMParams s)
|
||||
|
||||
instance Eq AConnectionRequestUri where
|
||||
ACR m cr == ACR m' cr' = case testEquality m m' of
|
||||
Just Refl -> cr == cr'
|
||||
_ -> False
|
||||
|
||||
uri :: String
|
||||
uri = "smp.simplex.im"
|
||||
|
||||
@@ -61,11 +76,11 @@ connReqData =
|
||||
testDhPubKey :: C.PublicKeyX448
|
||||
testDhPubKey = "MEIwBQYDK2VvAzkAmKuSYeQ/m0SixPDS8Wq8VBaTS1cW+Lp0n0h4Diu+kUpR+qXx4SDJ32YGEFoGFGSbGPry5Ychr6U="
|
||||
|
||||
testE2ERatchetParams :: E2ERatchetParamsUri 'C.X448
|
||||
testE2ERatchetParams = E2ERatchetParamsUri (mkVersionRange 1 1) testDhPubKey testDhPubKey
|
||||
testE2ERatchetParams :: RcvE2ERatchetParamsUri 'C.X448
|
||||
testE2ERatchetParams = E2ERatchetParamsUri (mkVersionRange 1 1) testDhPubKey testDhPubKey Nothing
|
||||
|
||||
testE2ERatchetParams12 :: E2ERatchetParamsUri 'C.X448
|
||||
testE2ERatchetParams12 = E2ERatchetParamsUri supportedE2EEncryptVRange testDhPubKey testDhPubKey
|
||||
testE2ERatchetParams12 :: RcvE2ERatchetParamsUri 'C.X448
|
||||
testE2ERatchetParams12 = E2ERatchetParamsUri supportedE2EEncryptVRange testDhPubKey testDhPubKey Nothing
|
||||
|
||||
connectionRequest :: AConnectionRequestUri
|
||||
connectionRequest =
|
||||
@@ -123,7 +138,7 @@ connectionRequestTests =
|
||||
<> urlEncode True testDhKeyStrUri
|
||||
<> "%2Csmp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1%26dh%3D"
|
||||
<> urlEncode True testDhKeyStrUri
|
||||
<> "&e2e=v%3D2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D"
|
||||
<> "&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D"
|
||||
strEncode connectionRequestClientDataEmpty
|
||||
`shouldBe` "simplex:/invitation#/?v=2&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1%26dh%3D"
|
||||
<> urlEncode True testDhKeyStrUri
|
||||
@@ -167,7 +182,7 @@ connectionRequestTests =
|
||||
<> testDhKeyStrUri
|
||||
<> "%2Csmp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1%26dh%3D"
|
||||
<> testDhKeyStrUri
|
||||
<> "&e2e=extra_key%3Dnew%26v%3D2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D"
|
||||
<> "&e2e=extra_key%3Dnew%26v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D"
|
||||
<> "&some_new_param=abc"
|
||||
<> "&v=2-4"
|
||||
)
|
||||
|
||||
@@ -1,75 +1,175 @@
|
||||
{-# LANGUAGE DataKinds #-}
|
||||
{-# LANGUAGE GADTs #-}
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
{-# LANGUAGE NamedFieldPuns #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE PatternSynonyms #-}
|
||||
{-# LANGUAGE RankNTypes #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# LANGUAGE StandaloneDeriving #-}
|
||||
{-# LANGUAGE TypeApplications #-}
|
||||
{-# OPTIONS_GHC -Wno-orphans #-}
|
||||
{-# OPTIONS_GHC -fno-warn-unticked-promoted-constructors #-}
|
||||
|
||||
module AgentTests.DoubleRatchetTests where
|
||||
|
||||
import Control.Concurrent.STM
|
||||
import Control.Monad (when)
|
||||
import Control.Monad.Except
|
||||
import Control.Monad.IO.Class
|
||||
import Crypto.Random (ChaChaDRG)
|
||||
import Data.Aeson (FromJSON, ToJSON)
|
||||
import qualified Data.Aeson as J
|
||||
import Data.ByteString.Char8 (ByteString)
|
||||
import qualified Data.ByteString.Char8 as B
|
||||
import qualified Data.Map.Strict as M
|
||||
import Data.Type.Equality
|
||||
import Simplex.Messaging.Crypto (Algorithm (..), AlgorithmI, CryptoError, DhAlgorithm)
|
||||
import qualified Simplex.Messaging.Crypto as C
|
||||
import Simplex.Messaging.Crypto.SNTRUP761.Bindings
|
||||
import Simplex.Messaging.Crypto.Ratchet
|
||||
import Simplex.Messaging.Encoding
|
||||
import Simplex.Messaging.Parsers (parseAll)
|
||||
import Simplex.Messaging.Util ((<$$>))
|
||||
import Simplex.Messaging.Version
|
||||
import Test.Hspec
|
||||
|
||||
doubleRatchetTests :: Spec
|
||||
doubleRatchetTests = do
|
||||
describe "double-ratchet encryption/decryption" $ do
|
||||
it "should serialize and parse message header" testMessageHeader
|
||||
it "should encrypt and decrypt messages" $ do
|
||||
withRatchets @X25519 testEncryptDecrypt
|
||||
withRatchets @X448 testEncryptDecrypt
|
||||
it "should encrypt and decrypt skipped messages" $ do
|
||||
withRatchets @X25519 testSkippedMessages
|
||||
withRatchets @X448 testSkippedMessages
|
||||
it "should encrypt and decrypt many messages" $ do
|
||||
withRatchets @X25519 testManyMessages
|
||||
it "should allow skipped after ratchet advance" $ do
|
||||
withRatchets @X25519 testSkippedAfterRatchetAdvance
|
||||
it "should serialize and parse message header" $ do
|
||||
testAlgs $ testMessageHeader kdfX3DHE2EEncryptVersion
|
||||
testAlgs $ testMessageHeader $ max pqRatchetVersion currentE2EEncryptVersion
|
||||
describe "message tests" $ runMessageTests initRatchets False
|
||||
it "should encode/decode ratchet as JSON" $ do
|
||||
testKeyJSON C.SX25519
|
||||
testKeyJSON C.SX448
|
||||
testRatchetJSON C.SX25519
|
||||
testRatchetJSON C.SX448
|
||||
it "should agree the same ratchet parameters" $ do
|
||||
testX3dh C.SX25519
|
||||
testX3dh C.SX448
|
||||
it "should agree the same ratchet parameters with version 1" $ do
|
||||
testX3dhV1 C.SX25519
|
||||
testX3dhV1 C.SX448
|
||||
testAlgs testKeyJSON
|
||||
testAlgs testRatchetJSON
|
||||
it "should agree the same ratchet parameters" $ testAlgs testX3dh
|
||||
it "should agree the same ratchet parameters with version 1" $ testAlgs testX3dhV1
|
||||
describe "post-quantum hybrid KEM double-ratchet algorithm" $ do
|
||||
describe "hybrid KEM key agreement" $ do
|
||||
it "should propose KEM during agreement, but no shared secret" $ testAlgs testPqX3dhProposeInReply
|
||||
it "should agree shared secret using KEM" $ testAlgs testPqX3dhProposeAccept
|
||||
it "should reject proposed KEM in reply" $ testAlgs testPqX3dhProposeReject
|
||||
it "should allow second proposal in reply" $ testAlgs testPqX3dhProposeAgain
|
||||
describe "hybrid KEM key agreement errors" $ do
|
||||
it "should fail if reply contains acceptance without proposal" $ testAlgs testPqX3dhAcceptWithoutProposalError
|
||||
describe "ratchet encryption/decryption" $ do
|
||||
it "should serialize and parse public KEM params" testKEMParams
|
||||
it "should serialize and parse message header" $ testAlgs testMessageHeaderKEM
|
||||
describe "message tests, KEM proposed" $ runMessageTests initRatchetsKEMProposed True
|
||||
describe "message tests, KEM accepted" $ runMessageTests initRatchetsKEMAccepted False
|
||||
describe "message tests, KEM proposed again in reply" $ runMessageTests initRatchetsKEMProposedAgain True
|
||||
it "should disable and re-enable KEM" $ withRatchets_ @X25519 initRatchetsKEMAccepted testDisableEnableKEM
|
||||
it "should disable and re-enable KEM (always set PQEncryption)" $ withRatchets_ @X25519 initRatchetsKEMAccepted testDisableEnableKEMStrict
|
||||
it "should enable KEM when it was not enabled in handshake" $ withRatchets_ @X25519 initRatchets testEnableKEM
|
||||
it "should enable KEM when it was not enabled in handshake (always set PQEncryption)" $ withRatchets_ @X25519 initRatchets testEnableKEMStrict
|
||||
|
||||
runMessageTests ::
|
||||
(forall a. (AlgorithmI a, DhAlgorithm a) => IO (Ratchet a, Ratchet a, Encrypt a, Decrypt a, EncryptDecryptSpec a)) ->
|
||||
Bool ->
|
||||
Spec
|
||||
runMessageTests initRatchets_ agreeRatchetKEMs = do
|
||||
it "should encrypt and decrypt messages" $ run $ testEncryptDecrypt agreeRatchetKEMs
|
||||
it "should encrypt and decrypt skipped messages" $ run $ testSkippedMessages agreeRatchetKEMs
|
||||
it "should encrypt and decrypt many messages" $ run $ testManyMessages agreeRatchetKEMs
|
||||
it "should allow skipped after ratchet advance" $ run $ testSkippedAfterRatchetAdvance agreeRatchetKEMs
|
||||
where
|
||||
run :: (forall a. (AlgorithmI a, DhAlgorithm a) => TestRatchets a) -> IO ()
|
||||
run test = do
|
||||
withRatchets_ @X25519 initRatchets_ test
|
||||
withRatchets_ @X448 initRatchets_ test
|
||||
|
||||
|
||||
testAlgs :: (forall a. (AlgorithmI a, DhAlgorithm a) => C.SAlgorithm a -> IO ()) -> IO ()
|
||||
testAlgs test = test C.SX25519 >> test C.SX448
|
||||
|
||||
paddedMsgLen :: Int
|
||||
paddedMsgLen = 100
|
||||
|
||||
fullMsgLen :: Int
|
||||
fullMsgLen = 1 + fullHeaderLen + C.authTagSize + paddedMsgLen
|
||||
fullMsgLen :: Version -> Int
|
||||
fullMsgLen v = headerLenLength + fullHeaderLen + C.authTagSize + paddedMsgLen
|
||||
where
|
||||
headerLenLength = if v < pqRatchetVersion then 1 else 3 -- two bytes are added because of two Large used in new encoding
|
||||
|
||||
testMessageHeader :: Expectation
|
||||
testMessageHeader = do
|
||||
(k, _) <- atomically . C.generateKeyPair @X25519 =<< C.newRandom
|
||||
let hdr = MsgHeader {msgMaxVersion = currentE2EEncryptVersion, msgDHRs = k, msgPN = 0, msgNs = 0}
|
||||
parseAll (smpP @(MsgHeader 'X25519)) (smpEncode hdr) `shouldBe` Right hdr
|
||||
testMessageHeader :: forall a. AlgorithmI a => Version -> C.SAlgorithm a -> Expectation
|
||||
testMessageHeader v _ = do
|
||||
(k, _) <- atomically . C.generateKeyPair @a =<< C.newRandom
|
||||
let hdr = MsgHeader {msgMaxVersion = v, msgDHRs = k, msgKEM = Nothing, msgPN = 0, msgNs = 0}
|
||||
parseAll (smpP @(MsgHeader a)) (smpEncode hdr) `shouldBe` Right hdr
|
||||
|
||||
testKEMParams :: Expectation
|
||||
testKEMParams = do
|
||||
g <- C.newRandom
|
||||
(kem, _) <- sntrup761Keypair g
|
||||
let kemParams = ARKP SRKSProposed $ RKParamsProposed kem
|
||||
parseAll (smpP @ARKEMParams) (smpEncode kemParams) `shouldBe` Right kemParams
|
||||
(kem', _) <- sntrup761Keypair g
|
||||
(ct, _) <- sntrup761Enc g kem
|
||||
let kemParams' = ARKP SRKSAccepted $ RKParamsAccepted ct kem'
|
||||
parseAll (smpP @ARKEMParams) (smpEncode kemParams') `shouldBe` Right kemParams'
|
||||
|
||||
testMessageHeaderKEM :: forall a. AlgorithmI a => C.SAlgorithm a -> Expectation
|
||||
testMessageHeaderKEM _ = do
|
||||
g <- C.newRandom
|
||||
(k, _) <- atomically $ C.generateKeyPair @a g
|
||||
(kem, _) <- sntrup761Keypair g
|
||||
let msgMaxVersion = max pqRatchetVersion currentE2EEncryptVersion
|
||||
msgKEM = Just . ARKP SRKSProposed $ RKParamsProposed kem
|
||||
hdr = MsgHeader {msgMaxVersion, msgDHRs = k, msgKEM, msgPN = 0, msgNs = 0}
|
||||
parseAll (smpP @(MsgHeader a)) (smpEncode hdr) `shouldBe` Right hdr
|
||||
(kem', _) <- sntrup761Keypair g
|
||||
(ct, _) <- sntrup761Enc g kem
|
||||
let msgKEM' = Just . ARKP SRKSAccepted $ RKParamsAccepted ct kem'
|
||||
hdr' = MsgHeader {msgMaxVersion, msgDHRs = k, msgKEM = msgKEM', msgPN = 0, msgNs = 0}
|
||||
parseAll (smpP @(MsgHeader a)) (smpEncode hdr') `shouldBe` Right hdr'
|
||||
|
||||
pattern Decrypted :: ByteString -> Either CryptoError (Either CryptoError ByteString)
|
||||
pattern Decrypted msg <- Right (Right msg)
|
||||
|
||||
type TestRatchets a = (AlgorithmI a, DhAlgorithm a) => TVar (TVar ChaChaDRG, Ratchet a, SkippedMsgKeys) -> TVar (TVar ChaChaDRG, Ratchet a, SkippedMsgKeys) -> IO ()
|
||||
type Encrypt a = TVar (TVar ChaChaDRG, Ratchet a, SkippedMsgKeys) -> ByteString -> IO (Either CryptoError ByteString)
|
||||
|
||||
testEncryptDecrypt :: TestRatchets a
|
||||
testEncryptDecrypt alice bob = do
|
||||
type Decrypt a = TVar (TVar ChaChaDRG, Ratchet a, SkippedMsgKeys) -> ByteString -> IO (Either CryptoError (Either CryptoError ByteString))
|
||||
|
||||
type EncryptDecryptSpec a = (TVar (TVar ChaChaDRG, Ratchet a, SkippedMsgKeys), ByteString) -> TVar (TVar ChaChaDRG, Ratchet a, SkippedMsgKeys) -> Expectation
|
||||
|
||||
type TestRatchets a =
|
||||
TVar (TVar ChaChaDRG, Ratchet a, SkippedMsgKeys) ->
|
||||
TVar (TVar ChaChaDRG, Ratchet a, SkippedMsgKeys) ->
|
||||
Encrypt a ->
|
||||
Decrypt a ->
|
||||
EncryptDecryptSpec a ->
|
||||
IO ()
|
||||
|
||||
deriving instance Eq (Ratchet a)
|
||||
|
||||
deriving instance Eq (SndRatchet a)
|
||||
|
||||
deriving instance Eq RcvRatchet
|
||||
|
||||
deriving instance Eq RatchetKEM
|
||||
|
||||
deriving instance Eq RatchetKEMAccepted
|
||||
|
||||
deriving instance Eq RatchetInitParams
|
||||
|
||||
deriving instance Eq RatchetKey
|
||||
|
||||
deriving instance Eq (RKEMParams s)
|
||||
|
||||
instance Eq ARKEMParams where
|
||||
(ARKP s ps) == (ARKP s' ps') = case testEquality s s' of
|
||||
Just Refl -> ps == ps'
|
||||
Nothing -> False
|
||||
|
||||
deriving instance Eq (MsgHeader a)
|
||||
|
||||
initRatchetKEM :: (AlgorithmI a, DhAlgorithm a) => TVar (TVar ChaChaDRG, Ratchet a, SkippedMsgKeys) -> TVar (TVar ChaChaDRG, Ratchet a, SkippedMsgKeys) -> IO ()
|
||||
initRatchetKEM s r = encryptDecrypt (Just $ PQEncOn) (const ()) (const ()) (s, "initialising ratchet") r
|
||||
|
||||
testEncryptDecrypt :: (AlgorithmI a, DhAlgorithm a) => Bool -> TestRatchets a
|
||||
testEncryptDecrypt agreeRatchetKEMs alice bob encrypt decrypt (#>) = do
|
||||
when agreeRatchetKEMs $ initRatchetKEM bob alice >> initRatchetKEM alice bob
|
||||
(bob, "hello alice") #> alice
|
||||
(alice, "hello bob") #> bob
|
||||
Right b1 <- encrypt bob "how are you, alice?"
|
||||
@@ -88,8 +188,9 @@ testEncryptDecrypt alice bob = do
|
||||
(alice, "I'm here too, same") #> bob
|
||||
pure ()
|
||||
|
||||
testSkippedMessages :: TestRatchets a
|
||||
testSkippedMessages alice bob = do
|
||||
testSkippedMessages :: (AlgorithmI a, DhAlgorithm a) => Bool -> TestRatchets a
|
||||
testSkippedMessages agreeRatchetKEMs alice bob encrypt decrypt _ = do
|
||||
when agreeRatchetKEMs $ initRatchetKEM bob alice >> initRatchetKEM alice bob
|
||||
Right msg1 <- encrypt bob "hello alice"
|
||||
Right msg2 <- encrypt bob "hello there again"
|
||||
Right msg3 <- encrypt bob "are you there?"
|
||||
@@ -99,8 +200,9 @@ testSkippedMessages alice bob = do
|
||||
Decrypted "hello alice" <- decrypt alice msg1
|
||||
pure ()
|
||||
|
||||
testManyMessages :: TestRatchets a
|
||||
testManyMessages alice bob = do
|
||||
testManyMessages :: (AlgorithmI a, DhAlgorithm a) => Bool -> TestRatchets a
|
||||
testManyMessages agreeRatchetKEMs alice bob _ _ (#>) = do
|
||||
when agreeRatchetKEMs $ initRatchetKEM bob alice >> initRatchetKEM alice bob
|
||||
(bob, "b1") #> alice
|
||||
(bob, "b2") #> alice
|
||||
(bob, "b3") #> alice
|
||||
@@ -117,8 +219,9 @@ testManyMessages alice bob = do
|
||||
(bob, "b15") #> alice
|
||||
(bob, "b16") #> alice
|
||||
|
||||
testSkippedAfterRatchetAdvance :: TestRatchets a
|
||||
testSkippedAfterRatchetAdvance alice bob = do
|
||||
testSkippedAfterRatchetAdvance :: (AlgorithmI a, DhAlgorithm a) => Bool -> TestRatchets a
|
||||
testSkippedAfterRatchetAdvance agreeRatchetKEMs alice bob encrypt decrypt (#>) = do
|
||||
when agreeRatchetKEMs $ initRatchetKEM bob alice >> initRatchetKEM alice bob
|
||||
(bob, "b1") #> alice
|
||||
Right b2 <- encrypt bob "b2"
|
||||
Right b3 <- encrypt bob "b3"
|
||||
@@ -152,6 +255,74 @@ testSkippedAfterRatchetAdvance alice bob = do
|
||||
Decrypted "b11" <- decrypt alice b11
|
||||
pure ()
|
||||
|
||||
testDisableEnableKEM :: forall a. (AlgorithmI a, DhAlgorithm a) => TestRatchets a
|
||||
testDisableEnableKEM alice bob _ _ _ = do
|
||||
(bob, "hello alice") !#> alice
|
||||
(alice, "hello bob") !#> bob
|
||||
(bob, "disabling KEM") !#>\ alice
|
||||
(alice, "still disabling KEM") !#> bob
|
||||
(bob, "now KEM is disabled") \#> alice
|
||||
(alice, "KEM is disabled for both sides") \#> bob
|
||||
(bob, "trying to enable KEM") \#>! alice
|
||||
(alice, "but unless alice enables it too it won't enable") \#> bob
|
||||
(bob, "KEM is disabled") \#> alice
|
||||
(alice, "KEM is disabled for both sides") \#> bob
|
||||
(bob, "enabling KEM again") \#>! alice
|
||||
(alice, "and alice accepts it this time") \#>! bob
|
||||
(bob, "still enabling KEM") \#>! alice
|
||||
(alice, "now KEM is enabled") !#> bob
|
||||
(bob, "KEM is enabled for both sides") !#> alice
|
||||
|
||||
testDisableEnableKEMStrict :: forall a. (AlgorithmI a, DhAlgorithm a) => TestRatchets a
|
||||
testDisableEnableKEMStrict alice bob _ _ _ = do
|
||||
(bob, "hello alice") !#>! alice
|
||||
(alice, "hello bob") !#>! bob
|
||||
(bob, "disabling KEM") !#>\ alice
|
||||
(alice, "still disabling KEM") !#>! bob
|
||||
(bob, "now KEM is disabled") \#>\ alice
|
||||
(alice, "KEM is disabled for both sides") \#>\ bob
|
||||
(bob, "trying to enable KEM") \#>! alice
|
||||
(alice, "but unless alice enables it too it won't enable") \#>\ bob
|
||||
(bob, "KEM is disabled") \#>! alice
|
||||
(alice, "KEM is disabled for both sides") \#>\ bob
|
||||
(bob, "enabling KEM again") \#>! alice
|
||||
(alice, "and alice accepts it this time") \#>! bob
|
||||
(bob, "still enabling KEM") \#>! alice
|
||||
(alice, "now KEM is enabled") !#>! bob
|
||||
(bob, "KEM is enabled for both sides") !#>! alice
|
||||
|
||||
testEnableKEM :: forall a. (AlgorithmI a, DhAlgorithm a) => TestRatchets a
|
||||
testEnableKEM alice bob _ _ _ = do
|
||||
(bob, "hello alice") \#> alice
|
||||
(alice, "hello bob") \#> bob
|
||||
(bob, "enabling KEM") \#>! alice
|
||||
(bob, "KEM not enabled yet") \#>! alice
|
||||
(alice, "accepting KEM") \#>! bob
|
||||
(alice, "KEM not enabled yet here too") \#>! bob
|
||||
(bob, "KEM is still not enabled") \#>! alice
|
||||
(alice, "now KEM is enabled") !#> bob
|
||||
(bob, "now KEM is enabled for both sides") !#> alice
|
||||
(alice, "disabling KEM") !#>\ bob
|
||||
(bob, "KEM not disabled yet") !#> alice
|
||||
(alice, "KEM disabled") \#> bob
|
||||
(bob, "KEM disabled on both sides") \#> alice
|
||||
|
||||
testEnableKEMStrict :: forall a. (AlgorithmI a, DhAlgorithm a) => TestRatchets a
|
||||
testEnableKEMStrict alice bob _ _ _ = do
|
||||
(bob, "hello alice") \#>\ alice
|
||||
(alice, "hello bob") \#>\ bob
|
||||
(bob, "enabling KEM") \#>! alice
|
||||
(bob, "KEM not enabled yet") \#>! alice
|
||||
(alice, "accepting KEM") \#>! bob
|
||||
(alice, "KEM not enabled yet here too") \#>! bob
|
||||
(bob, "KEM is still not enabled") \#>! alice
|
||||
(alice, "now KEM is enabled") !#>! bob
|
||||
(bob, "now KEM is enabled for both sides") !#>! alice
|
||||
(alice, "disabling KEM") !#>\ bob
|
||||
(bob, "KEM not disabled yet") !#>! alice
|
||||
(alice, "KEM disabled") \#>\ bob
|
||||
(bob, "KEM disabled on both sides") \#>! alice
|
||||
|
||||
testKeyJSON :: forall a. AlgorithmI a => C.SAlgorithm a -> IO ()
|
||||
testKeyJSON _ = do
|
||||
(k, pk) <- atomically . C.generateKeyPair @a =<< C.newRandom
|
||||
@@ -160,7 +331,7 @@ testKeyJSON _ = do
|
||||
|
||||
testRatchetJSON :: forall a. (AlgorithmI a, DhAlgorithm a) => C.SAlgorithm a -> IO ()
|
||||
testRatchetJSON _ = do
|
||||
(alice, bob) <- initRatchets @a
|
||||
(alice, bob, _, _, _) <- initRatchets @a
|
||||
testEncodeDecode alice
|
||||
testEncodeDecode bob
|
||||
|
||||
@@ -173,77 +344,246 @@ testEncodeDecode x = do
|
||||
testX3dh :: forall a. (AlgorithmI a, DhAlgorithm a) => C.SAlgorithm a -> IO ()
|
||||
testX3dh _ = do
|
||||
g <- C.newRandom
|
||||
(pkBob1, pkBob2, e2eBob) <- atomically $ generateE2EParams @a g currentE2EEncryptVersion
|
||||
(pkAlice1, pkAlice2, e2eAlice) <- atomically $ generateE2EParams @a g currentE2EEncryptVersion
|
||||
let paramsBob = x3dhSnd pkBob1 pkBob2 e2eAlice
|
||||
paramsAlice = x3dhRcv pkAlice1 pkAlice2 e2eBob
|
||||
let v = max pqRatchetVersion currentE2EEncryptVersion
|
||||
(pkBob1, pkBob2, Nothing, AE2ERatchetParams _ e2eBob) <- liftIO $ generateSndE2EParams @a g v Nothing
|
||||
(pkAlice1, pkAlice2, Nothing, e2eAlice) <- liftIO $ generateRcvE2EParams @a g v PQEncOff
|
||||
let paramsBob = pqX3dhSnd pkBob1 pkBob2 Nothing e2eAlice
|
||||
paramsAlice <- runExceptT $ pqX3dhRcv pkAlice1 pkAlice2 Nothing e2eBob
|
||||
paramsAlice `shouldBe` paramsBob
|
||||
|
||||
testX3dhV1 :: forall a. (AlgorithmI a, DhAlgorithm a) => C.SAlgorithm a -> IO ()
|
||||
testX3dhV1 _ = do
|
||||
g <- C.newRandom
|
||||
(pkBob1, pkBob2, e2eBob) <- atomically $ generateE2EParams @a g 1
|
||||
(pkAlice1, pkAlice2, e2eAlice) <- atomically $ generateE2EParams @a g 1
|
||||
let paramsBob = x3dhSnd pkBob1 pkBob2 e2eAlice
|
||||
paramsAlice = x3dhRcv pkAlice1 pkAlice2 e2eBob
|
||||
(pkBob1, pkBob2, Nothing, AE2ERatchetParams _ e2eBob) <- liftIO $ generateSndE2EParams @a g 1 Nothing
|
||||
(pkAlice1, pkAlice2, Nothing, e2eAlice) <- liftIO $ generateRcvE2EParams @a g 1 PQEncOff
|
||||
let paramsBob = pqX3dhSnd pkBob1 pkBob2 Nothing e2eAlice
|
||||
paramsAlice <- runExceptT $ pqX3dhRcv pkAlice1 pkAlice2 Nothing e2eBob
|
||||
paramsAlice `shouldBe` paramsBob
|
||||
|
||||
(#>) :: (AlgorithmI a, DhAlgorithm a) => (TVar (TVar ChaChaDRG, Ratchet a, SkippedMsgKeys), ByteString) -> TVar (TVar ChaChaDRG, Ratchet a, SkippedMsgKeys) -> Expectation
|
||||
(alice, msg) #> bob = do
|
||||
Right msg' <- encrypt alice msg
|
||||
Decrypted msg'' <- decrypt bob msg'
|
||||
testPqX3dhProposeInReply :: forall a. (AlgorithmI a, DhAlgorithm a) => C.SAlgorithm a -> IO ()
|
||||
testPqX3dhProposeInReply _ = do
|
||||
g <- C.newRandom
|
||||
let v = max pqRatchetVersion currentE2EEncryptVersion
|
||||
-- initiate (no KEM)
|
||||
(pkAlice1, pkAlice2, Nothing, e2eAlice) <- liftIO $ generateRcvE2EParams @a g v PQEncOff
|
||||
-- propose KEM in reply
|
||||
(pkBob1, pkBob2, pKemBob_@(Just _), AE2ERatchetParams _ e2eBob) <- liftIO $ generateSndE2EParams @a g v (Just $ AUseKEM SRKSProposed ProposeKEM)
|
||||
Right paramsBob <- pure $ pqX3dhSnd pkBob1 pkBob2 pKemBob_ e2eAlice
|
||||
Right paramsAlice <- runExceptT $ pqX3dhRcv pkAlice1 pkAlice2 Nothing e2eBob
|
||||
paramsAlice `compatibleRatchets` paramsBob
|
||||
|
||||
testPqX3dhProposeAccept :: forall a. (AlgorithmI a, DhAlgorithm a) => C.SAlgorithm a -> IO ()
|
||||
testPqX3dhProposeAccept _ = do
|
||||
g <- C.newRandom
|
||||
let v = max pqRatchetVersion currentE2EEncryptVersion
|
||||
-- initiate (propose KEM)
|
||||
(pkAlice1, pkAlice2, pKemAlice_@(Just _), e2eAlice) <- liftIO $ generateRcvE2EParams @a g v PQEncOn
|
||||
E2ERatchetParams _ _ _ (Just (RKParamsProposed aliceKem)) <- pure e2eAlice
|
||||
-- accept KEM
|
||||
(pkBob1, pkBob2, pKemBob_@(Just _), AE2ERatchetParams _ e2eBob) <- liftIO $ generateSndE2EParams @a g v (Just $ AUseKEM SRKSAccepted $ AcceptKEM aliceKem)
|
||||
Right paramsBob <- pure $ pqX3dhSnd pkBob1 pkBob2 pKemBob_ e2eAlice
|
||||
Right paramsAlice <- runExceptT $ pqX3dhRcv pkAlice1 pkAlice2 pKemAlice_ e2eBob
|
||||
paramsAlice `compatibleRatchets` paramsBob
|
||||
|
||||
testPqX3dhProposeReject :: forall a. (AlgorithmI a, DhAlgorithm a) => C.SAlgorithm a -> IO ()
|
||||
testPqX3dhProposeReject _ = do
|
||||
g <- C.newRandom
|
||||
let v = max pqRatchetVersion currentE2EEncryptVersion
|
||||
-- initiate (propose KEM)
|
||||
(pkAlice1, pkAlice2, pKemAlice_@(Just _), e2eAlice) <- liftIO $ generateRcvE2EParams @a g v PQEncOn
|
||||
E2ERatchetParams _ _ _ (Just (RKParamsProposed _)) <- pure e2eAlice
|
||||
-- reject KEM
|
||||
(pkBob1, pkBob2, Nothing, AE2ERatchetParams _ e2eBob) <- liftIO $ generateSndE2EParams @a g v Nothing
|
||||
Right paramsBob <- pure $ pqX3dhSnd pkBob1 pkBob2 Nothing e2eAlice
|
||||
Right paramsAlice <- runExceptT $ pqX3dhRcv pkAlice1 pkAlice2 pKemAlice_ e2eBob
|
||||
paramsAlice `compatibleRatchets` paramsBob
|
||||
|
||||
testPqX3dhAcceptWithoutProposalError :: forall a. (AlgorithmI a, DhAlgorithm a) => C.SAlgorithm a -> IO ()
|
||||
testPqX3dhAcceptWithoutProposalError _ = do
|
||||
g <- C.newRandom
|
||||
let v = max pqRatchetVersion currentE2EEncryptVersion
|
||||
-- initiate (no KEM)
|
||||
(pkAlice1, pkAlice2, Nothing, e2eAlice) <- liftIO $ generateRcvE2EParams @a g v PQEncOff
|
||||
E2ERatchetParams _ _ _ Nothing <- pure e2eAlice
|
||||
-- incorrectly accept KEM
|
||||
-- we don't have key in proposal, so we just generate it
|
||||
(k, _) <- sntrup761Keypair g
|
||||
(pkBob1, pkBob2, pKemBob_@(Just _), AE2ERatchetParams _ e2eBob) <- liftIO $ generateSndE2EParams @a g v (Just $ AUseKEM SRKSAccepted $ AcceptKEM k)
|
||||
pqX3dhSnd pkBob1 pkBob2 pKemBob_ e2eAlice `shouldBe` Left C.CERatchetKEMState
|
||||
runExceptT (pqX3dhRcv pkAlice1 pkAlice2 Nothing e2eBob) `shouldReturn` Left C.CERatchetKEMState
|
||||
|
||||
testPqX3dhProposeAgain :: forall a. (AlgorithmI a, DhAlgorithm a) => C.SAlgorithm a -> IO ()
|
||||
testPqX3dhProposeAgain _ = do
|
||||
g <- C.newRandom
|
||||
let v = max pqRatchetVersion currentE2EEncryptVersion
|
||||
-- initiate (propose KEM)
|
||||
(pkAlice1, pkAlice2, pKemAlice_@(Just _), e2eAlice) <- liftIO $ generateRcvE2EParams @a g v PQEncOn
|
||||
E2ERatchetParams _ _ _ (Just (RKParamsProposed _)) <- pure e2eAlice
|
||||
-- propose KEM again in reply - this is not an error
|
||||
(pkBob1, pkBob2, pKemBob_@(Just _), AE2ERatchetParams _ e2eBob) <- liftIO $ generateSndE2EParams @a g v (Just $ AUseKEM SRKSProposed ProposeKEM)
|
||||
Right paramsBob <- pure $ pqX3dhSnd pkBob1 pkBob2 pKemBob_ e2eAlice
|
||||
Right paramsAlice <- runExceptT $ pqX3dhRcv pkAlice1 pkAlice2 pKemAlice_ e2eBob
|
||||
paramsAlice `compatibleRatchets` paramsBob
|
||||
|
||||
compatibleRatchets :: (RatchetInitParams, x) -> (RatchetInitParams, x) -> Expectation
|
||||
compatibleRatchets
|
||||
(RatchetInitParams {assocData, ratchetKey, sndHK, rcvNextHK, kemAccepted}, _)
|
||||
(RatchetInitParams {assocData = ad, ratchetKey = rk, sndHK = shk, rcvNextHK = rnhk, kemAccepted = ka}, _) = do
|
||||
assocData == ad && ratchetKey == rk && sndHK == shk && rcvNextHK == rnhk `shouldBe` True
|
||||
case (kemAccepted, ka) of
|
||||
(Just RatchetKEMAccepted {rcPQRr, rcPQRss, rcPQRct}, Just RatchetKEMAccepted {rcPQRr = pqk, rcPQRss = pqss, rcPQRct = pqct}) ->
|
||||
pqk /= rcPQRr && pqss == rcPQRss && pqct == rcPQRct `shouldBe` True
|
||||
(Nothing, Nothing) -> pure ()
|
||||
_ -> expectationFailure "RatchetInitParams params are not compatible"
|
||||
|
||||
encryptDecrypt :: (AlgorithmI a, DhAlgorithm a) => Maybe PQEncryption -> (Ratchet a -> ()) -> (Ratchet a -> ()) -> EncryptDecryptSpec a
|
||||
encryptDecrypt pqEnc invalidSnd invalidRcv (alice, msg) bob = do
|
||||
Right msg' <- withTVar (encrypt_ pqEnc) invalidSnd alice msg
|
||||
Decrypted msg'' <- decrypt' invalidRcv bob msg'
|
||||
msg'' `shouldBe` msg
|
||||
|
||||
withRatchets :: forall a. (AlgorithmI a, DhAlgorithm a) => (TVar (TVar ChaChaDRG, Ratchet a, SkippedMsgKeys) -> TVar (TVar ChaChaDRG, Ratchet a, SkippedMsgKeys) -> IO ()) -> Expectation
|
||||
withRatchets test = do
|
||||
-- enable KEM (currently disabled)
|
||||
(\#>!) :: (AlgorithmI a, DhAlgorithm a) => EncryptDecryptSpec a
|
||||
(s, msg) \#>! r = encryptDecrypt (Just PQEncOn) noSndKEM noRcvKEM (s, msg) r
|
||||
|
||||
-- enable KEM (currently enabled)
|
||||
(!#>!) :: (AlgorithmI a, DhAlgorithm a) => EncryptDecryptSpec a
|
||||
(s, msg) !#>! r = encryptDecrypt (Just PQEncOn) hasSndKEM hasRcvKEM (s, msg) r
|
||||
|
||||
-- KEM enabled (no user preference)
|
||||
(!#>) :: (AlgorithmI a, DhAlgorithm a) => EncryptDecryptSpec a
|
||||
(s, msg) !#> r = encryptDecrypt Nothing hasSndKEM hasRcvKEM (s, msg) r
|
||||
|
||||
-- disable KEM (currently enabled)
|
||||
(!#>\) :: (AlgorithmI a, DhAlgorithm a) => EncryptDecryptSpec a
|
||||
(s, msg) !#>\ r = encryptDecrypt (Just PQEncOff) hasSndKEM hasRcvKEM (s, msg) r
|
||||
|
||||
-- disable KEM (currently disabled)
|
||||
(\#>\) :: (AlgorithmI a, DhAlgorithm a) => EncryptDecryptSpec a
|
||||
(s, msg) \#>\ r = encryptDecrypt (Just PQEncOff) noSndKEM noSndKEM (s, msg) r
|
||||
|
||||
-- KEM disabled (no user preference)
|
||||
(\#>) :: (AlgorithmI a, DhAlgorithm a) => EncryptDecryptSpec a
|
||||
(s, msg) \#> r = encryptDecrypt Nothing noSndKEM noSndKEM (s, msg) r
|
||||
|
||||
withRatchets_ :: IO (Ratchet a, Ratchet a, Encrypt a, Decrypt a, EncryptDecryptSpec a) -> TestRatchets a -> Expectation
|
||||
withRatchets_ initRatchets_ test = do
|
||||
ga <- C.newRandom
|
||||
gb <- C.newRandom
|
||||
(a, b) <- initRatchets @a
|
||||
(a, b, encrypt, decrypt, (#>)) <- initRatchets_
|
||||
alice <- newTVarIO (ga, a, M.empty)
|
||||
bob <- newTVarIO (gb, b, M.empty)
|
||||
test alice bob `shouldReturn` ()
|
||||
test alice bob encrypt decrypt (#>) `shouldReturn` ()
|
||||
|
||||
initRatchets :: (AlgorithmI a, DhAlgorithm a) => IO (Ratchet a, Ratchet a)
|
||||
initRatchets :: (AlgorithmI a, DhAlgorithm a) => IO (Ratchet a, Ratchet a, Encrypt a, Decrypt a, EncryptDecryptSpec a)
|
||||
initRatchets = do
|
||||
g <- C.newRandom
|
||||
(pkBob1, pkBob2, e2eBob) <- atomically $ generateE2EParams g currentE2EEncryptVersion
|
||||
(pkAlice1, pkAlice2, e2eAlice) <- atomically $ generateE2EParams g currentE2EEncryptVersion
|
||||
let paramsBob = x3dhSnd pkBob1 pkBob2 e2eAlice
|
||||
paramsAlice = x3dhRcv pkAlice1 pkAlice2 e2eBob
|
||||
let v = max pqRatchetVersion currentE2EEncryptVersion
|
||||
(pkBob1, pkBob2, _pKemParams@Nothing, AE2ERatchetParams _ e2eBob) <- liftIO $ generateSndE2EParams g v Nothing
|
||||
(pkAlice1, pkAlice2, _pKem@Nothing, e2eAlice) <- liftIO $ generateRcvE2EParams g v PQEncOff
|
||||
Right paramsBob <- pure $ pqX3dhSnd pkBob1 pkBob2 Nothing e2eAlice
|
||||
Right paramsAlice <- runExceptT $ pqX3dhRcv pkAlice1 pkAlice2 Nothing e2eBob
|
||||
(_, pkBob3) <- atomically $ C.generateKeyPair g
|
||||
let bob = initSndRatchet supportedE2EEncryptVRange (C.publicKey pkAlice2) pkBob3 paramsBob
|
||||
alice = initRcvRatchet supportedE2EEncryptVRange pkAlice2 paramsAlice
|
||||
pure (alice, bob)
|
||||
alice = initRcvRatchet supportedE2EEncryptVRange pkAlice2 paramsAlice PQEncOff
|
||||
pure (alice, bob, encrypt' noSndKEM, decrypt' noRcvKEM, (\#>))
|
||||
|
||||
encrypt_ :: AlgorithmI a => (TVar ChaChaDRG, Ratchet a, SkippedMsgKeys) -> ByteString -> IO (Either CryptoError (ByteString, Ratchet a, SkippedMsgDiff))
|
||||
encrypt_ (_, rc, _) msg =
|
||||
runExceptT (rcEncrypt rc paddedMsgLen msg)
|
||||
initRatchetsKEMProposed :: forall a. (AlgorithmI a, DhAlgorithm a) => IO (Ratchet a, Ratchet a, Encrypt a, Decrypt a, EncryptDecryptSpec a)
|
||||
initRatchetsKEMProposed = do
|
||||
g <- C.newRandom
|
||||
let v = max pqRatchetVersion currentE2EEncryptVersion
|
||||
-- initiate (no KEM)
|
||||
(pkAlice1, pkAlice2, Nothing, e2eAlice) <- liftIO $ generateRcvE2EParams g v PQEncOff
|
||||
-- propose KEM in reply
|
||||
let useKem = AUseKEM SRKSProposed ProposeKEM
|
||||
(pkBob1, pkBob2, pKemParams_@(Just _), AE2ERatchetParams _ e2eBob) <- liftIO $ generateSndE2EParams g v (Just useKem)
|
||||
Right paramsBob <- pure $ pqX3dhSnd pkBob1 pkBob2 pKemParams_ e2eAlice
|
||||
Right paramsAlice <- runExceptT $ pqX3dhRcv pkAlice1 pkAlice2 Nothing e2eBob
|
||||
(_, pkBob3) <- atomically $ C.generateKeyPair g
|
||||
let bob = initSndRatchet supportedE2EEncryptVRange (C.publicKey pkAlice2) pkBob3 paramsBob
|
||||
alice = initRcvRatchet supportedE2EEncryptVRange pkAlice2 paramsAlice PQEncOn
|
||||
pure (alice, bob, encrypt' hasSndKEM, decrypt' hasRcvKEM, (!#>))
|
||||
|
||||
initRatchetsKEMAccepted :: forall a. (AlgorithmI a, DhAlgorithm a) => IO (Ratchet a, Ratchet a, Encrypt a, Decrypt a, EncryptDecryptSpec a)
|
||||
initRatchetsKEMAccepted = do
|
||||
g <- C.newRandom
|
||||
let v = max pqRatchetVersion currentE2EEncryptVersion
|
||||
-- initiate (propose)
|
||||
(pkAlice1, pkAlice2, pKem_@(Just _), e2eAlice) <- liftIO $ generateRcvE2EParams g v PQEncOn
|
||||
E2ERatchetParams _ _ _ (Just (RKParamsProposed aliceKem)) <- pure e2eAlice
|
||||
-- accept
|
||||
let useKem = AUseKEM SRKSAccepted (AcceptKEM aliceKem)
|
||||
(pkBob1, pkBob2, pKemParams_@(Just _), AE2ERatchetParams _ e2eBob) <- liftIO $ generateSndE2EParams g v (Just useKem)
|
||||
Right paramsBob <- pure $ pqX3dhSnd pkBob1 pkBob2 pKemParams_ e2eAlice
|
||||
Right paramsAlice <- runExceptT $ pqX3dhRcv pkAlice1 pkAlice2 pKem_ e2eBob
|
||||
(_, pkBob3) <- atomically $ C.generateKeyPair g
|
||||
let bob = initSndRatchet supportedE2EEncryptVRange (C.publicKey pkAlice2) pkBob3 paramsBob
|
||||
alice = initRcvRatchet supportedE2EEncryptVRange pkAlice2 paramsAlice PQEncOn
|
||||
pure (alice, bob, encrypt' hasSndKEM, decrypt' hasRcvKEM, (!#>))
|
||||
|
||||
initRatchetsKEMProposedAgain :: forall a. (AlgorithmI a, DhAlgorithm a) => IO (Ratchet a, Ratchet a, Encrypt a, Decrypt a, EncryptDecryptSpec a)
|
||||
initRatchetsKEMProposedAgain = do
|
||||
g <- C.newRandom
|
||||
let v = max pqRatchetVersion currentE2EEncryptVersion
|
||||
-- initiate (propose KEM)
|
||||
(pkAlice1, pkAlice2, pKem_@(Just _), e2eAlice) <- liftIO $ generateRcvE2EParams g v PQEncOn
|
||||
-- propose KEM again in reply
|
||||
let useKem = AUseKEM SRKSProposed ProposeKEM
|
||||
(pkBob1, pkBob2, pKemParams_@(Just _), AE2ERatchetParams _ e2eBob) <- liftIO $ generateSndE2EParams g v (Just useKem)
|
||||
Right paramsBob <- pure $ pqX3dhSnd pkBob1 pkBob2 pKemParams_ e2eAlice
|
||||
Right paramsAlice <- runExceptT $ pqX3dhRcv pkAlice1 pkAlice2 pKem_ e2eBob
|
||||
(_, pkBob3) <- atomically $ C.generateKeyPair g
|
||||
let bob = initSndRatchet supportedE2EEncryptVRange (C.publicKey pkAlice2) pkBob3 paramsBob
|
||||
alice = initRcvRatchet supportedE2EEncryptVRange pkAlice2 paramsAlice PQEncOn
|
||||
pure (alice, bob, encrypt' hasSndKEM, decrypt' hasRcvKEM, (!#>))
|
||||
|
||||
encrypt_ :: AlgorithmI a => Maybe PQEncryption -> (TVar ChaChaDRG, Ratchet a, SkippedMsgKeys) -> ByteString -> IO (Either CryptoError (ByteString, Ratchet a, SkippedMsgDiff))
|
||||
encrypt_ enableKem (_, rc, _) msg =
|
||||
-- print msg >>
|
||||
runExceptT (rcEncrypt rc paddedMsgLen msg enableKem)
|
||||
>>= either (pure . Left) checkLength
|
||||
where
|
||||
checkLength (msg', rc') = do
|
||||
B.length msg' `shouldBe` fullMsgLen
|
||||
B.length msg' `shouldBe` fullMsgLen (maxVersion $ rcVersion rc)
|
||||
pure $ Right (msg', rc', SMDNoChange)
|
||||
|
||||
decrypt_ :: (AlgorithmI a, DhAlgorithm a) => (TVar ChaChaDRG, Ratchet a, SkippedMsgKeys) -> ByteString -> IO (Either CryptoError (Either CryptoError ByteString, Ratchet a, SkippedMsgDiff))
|
||||
decrypt_ (g, rc, smks) msg = runExceptT $ rcDecrypt g rc smks msg
|
||||
|
||||
encrypt :: AlgorithmI a => TVar (TVar ChaChaDRG, Ratchet a, SkippedMsgKeys) -> ByteString -> IO (Either CryptoError ByteString)
|
||||
encrypt = withTVar encrypt_
|
||||
encrypt' :: AlgorithmI a => (Ratchet a -> ()) -> Encrypt a
|
||||
encrypt' = withTVar $ encrypt_ Nothing
|
||||
|
||||
decrypt :: (AlgorithmI a, DhAlgorithm a) => TVar (TVar ChaChaDRG, Ratchet a, SkippedMsgKeys) -> ByteString -> IO (Either CryptoError (Either CryptoError ByteString))
|
||||
decrypt = withTVar decrypt_
|
||||
decrypt' :: (AlgorithmI a, DhAlgorithm a) => (Ratchet a -> ()) -> Decrypt a
|
||||
decrypt' = withTVar decrypt_
|
||||
|
||||
noSndKEM :: Ratchet a -> ()
|
||||
noSndKEM Ratchet {rcSndKEM = PQEncOn} = error "snd ratchet has KEM"
|
||||
noSndKEM _ = ()
|
||||
|
||||
noRcvKEM :: Ratchet a -> ()
|
||||
noRcvKEM Ratchet {rcRcvKEM = PQEncOn} = error "rcv ratchet has KEM"
|
||||
noRcvKEM _ = ()
|
||||
|
||||
hasSndKEM :: Ratchet a -> ()
|
||||
hasSndKEM Ratchet {rcSndKEM = PQEncOn} = ()
|
||||
hasSndKEM _ = error "snd ratchet has no KEM"
|
||||
|
||||
hasRcvKEM :: Ratchet a -> ()
|
||||
hasRcvKEM Ratchet {rcRcvKEM = PQEncOn} = ()
|
||||
hasRcvKEM _ = error "rcv ratchet has no KEM"
|
||||
|
||||
withTVar ::
|
||||
AlgorithmI a =>
|
||||
((TVar ChaChaDRG, Ratchet a, SkippedMsgKeys) -> ByteString -> IO (Either e (r, Ratchet a, SkippedMsgDiff))) ->
|
||||
(Ratchet a -> ()) ->
|
||||
TVar (TVar ChaChaDRG, Ratchet a, SkippedMsgKeys) ->
|
||||
ByteString ->
|
||||
IO (Either e r)
|
||||
withTVar op rcVar msg = do
|
||||
withTVar op valid rcVar msg = do
|
||||
(g, rc, smks) <- readTVarIO rcVar
|
||||
applyDiff smks <$$> (testEncodeDecode rc >> op (g, rc, smks) msg)
|
||||
>>= \case
|
||||
Right (res, rc', smks') -> atomically (writeTVar rcVar (g, rc', smks')) >> pure (Right res)
|
||||
Right (res, rc', smks') -> valid rc' `seq` atomically (writeTVar rcVar (g, rc', smks')) >> pure (Right res)
|
||||
Left e -> pure $ Left e
|
||||
where
|
||||
applyDiff smks (res, rc', smDiff) = (res, rc', applySMDiff smks smDiff)
|
||||
@@ -0,0 +1,25 @@
|
||||
{-# LANGUAGE GADTs #-}
|
||||
{-# LANGUAGE StandaloneDeriving #-}
|
||||
{-# OPTIONS_GHC -Wno-orphans #-}
|
||||
|
||||
module AgentTests.EqInstances where
|
||||
|
||||
import Data.Type.Equality
|
||||
import Simplex.Messaging.Agent.Store
|
||||
|
||||
instance Eq SomeConn where
|
||||
SomeConn d c == SomeConn d' c' = case testEquality d d' of
|
||||
Just Refl -> c == c'
|
||||
_ -> False
|
||||
|
||||
deriving instance Eq (Connection d)
|
||||
|
||||
deriving instance Eq (SConnType d)
|
||||
|
||||
deriving instance Eq (StoredRcvQueue q)
|
||||
|
||||
deriving instance Eq (StoredSndQueue q)
|
||||
|
||||
deriving instance Eq (DBQueueId q)
|
||||
|
||||
deriving instance Eq ClientNtfCreds
|
||||
@@ -1,5 +1,6 @@
|
||||
{-# LANGUAGE DataKinds #-}
|
||||
{-# LANGUAGE DuplicateRecordFields #-}
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
{-# LANGUAGE GADTs #-}
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
{-# LANGUAGE NamedFieldPuns #-}
|
||||
@@ -9,7 +10,9 @@
|
||||
{-# LANGUAGE PatternSynonyms #-}
|
||||
{-# LANGUAGE RankNTypes #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# LANGUAGE StandaloneDeriving #-}
|
||||
{-# LANGUAGE TypeApplications #-}
|
||||
{-# OPTIONS_GHC -Wno-orphans #-}
|
||||
{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-}
|
||||
|
||||
module AgentTests.FunctionalAPITests
|
||||
@@ -20,6 +23,9 @@ module AgentTests.FunctionalAPITests
|
||||
makeConnection,
|
||||
exchangeGreetingsMsgId,
|
||||
switchComplete,
|
||||
createConnection,
|
||||
joinConnection,
|
||||
sendMessage,
|
||||
runRight,
|
||||
runRight_,
|
||||
get,
|
||||
@@ -29,7 +35,9 @@ module AgentTests.FunctionalAPITests
|
||||
nGet,
|
||||
(##>),
|
||||
(=##>),
|
||||
pattern CON,
|
||||
pattern Msg,
|
||||
pattern Msg',
|
||||
agentCfgV7,
|
||||
)
|
||||
where
|
||||
@@ -45,7 +53,7 @@ import Data.Either (isRight)
|
||||
import Data.Int (Int64)
|
||||
import Data.List (nub)
|
||||
import qualified Data.Map as M
|
||||
import Data.Maybe (isNothing)
|
||||
import Data.Maybe (isJust, isNothing)
|
||||
import qualified Data.Set as S
|
||||
import Data.Time.Clock (diffUTCTime, getCurrentTime)
|
||||
import Data.Time.Clock.System (SystemTime (..), getSystemTime)
|
||||
@@ -53,17 +61,21 @@ import Data.Type.Equality
|
||||
import qualified Database.SQLite.Simple as SQL
|
||||
import SMPAgentClient
|
||||
import SMPClient (cfg, testPort, testPort2, testStoreLogFile2, withSmpServer, withSmpServerConfigOn, withSmpServerOn, withSmpServerStoreLogOn, withSmpServerStoreMsgLogOn, withSmpServerV7)
|
||||
import Simplex.Messaging.Agent
|
||||
import Simplex.Messaging.Agent hiding (createConnection, joinConnection, sendMessage)
|
||||
import qualified Simplex.Messaging.Agent as Agent
|
||||
import Simplex.Messaging.Agent.Client (ProtocolTestFailure (..), ProtocolTestStep (..))
|
||||
import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..), InitialAgentServers (..), createAgentStore)
|
||||
import Simplex.Messaging.Agent.Protocol as Agent
|
||||
import Simplex.Messaging.Agent.Protocol hiding (CON)
|
||||
import qualified Simplex.Messaging.Agent.Protocol as Agent
|
||||
import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation (..), SQLiteStore (dbNew))
|
||||
import Simplex.Messaging.Agent.Store.SQLite.Common (withTransaction')
|
||||
import Simplex.Messaging.Client (NetworkConfig (..), ProtocolClientConfig (..), TransportSessionMode (TSMEntity, TSMUser), defaultSMPClientConfig)
|
||||
import qualified Simplex.Messaging.Crypto as C
|
||||
import Simplex.Messaging.Crypto.Ratchet (InitialKeys (..), PQEncryption (..), pattern PQEncOn, pattern PQEncOff)
|
||||
import qualified Simplex.Messaging.Crypto.Ratchet as CR
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Notifications.Transport (authBatchCmdsNTFVersion)
|
||||
import Simplex.Messaging.Protocol (BasicAuth, ErrorType (..), MsgBody, ProtocolServer (..), SubscriptionMode (..), supportedSMPClientVRange)
|
||||
import Simplex.Messaging.Protocol (AProtocolType (..), BasicAuth, ErrorType (..), MsgBody, ProtocolServer (..), SubscriptionMode (..), supportedSMPClientVRange)
|
||||
import qualified Simplex.Messaging.Protocol as SMP
|
||||
import Simplex.Messaging.Server.Env.STM (ServerConfig (..))
|
||||
import Simplex.Messaging.Server.Expiration
|
||||
@@ -72,10 +84,21 @@ import Simplex.Messaging.Version
|
||||
import System.Directory (copyFile, renameFile)
|
||||
import Test.Hspec
|
||||
import UnliftIO
|
||||
import Util
|
||||
import XFTPClient (testXFTPServer)
|
||||
|
||||
type AEntityTransmission e = (ACorrId, ConnId, ACommand 'Agent e)
|
||||
|
||||
deriving instance Eq (ACommand p e)
|
||||
|
||||
instance Eq AConnectionMode where
|
||||
ACM m == ACM m' = isJust $ testEquality m m'
|
||||
|
||||
instance Eq AProtocolType where
|
||||
AProtocolType p == AProtocolType p' = isJust $ testEquality p p'
|
||||
|
||||
-- deriving instance Eq (ValidFileDescription p)
|
||||
|
||||
(##>) :: (HasCallStack, MonadUnliftIO m) => m (AEntityTransmission e) -> AEntityTransmission e -> m ()
|
||||
a ##> t = withTimeout a (`shouldBe` t)
|
||||
|
||||
@@ -118,13 +141,19 @@ pGet c = do
|
||||
DISCONNECT {} -> pGet c
|
||||
_ -> pure t
|
||||
|
||||
pattern Msg :: MsgBody -> ACommand 'Agent e
|
||||
pattern Msg msgBody <- MSG MsgMeta {integrity = MsgOk} _ msgBody
|
||||
pattern CON :: ACommand 'Agent 'AEConn
|
||||
pattern CON = Agent.CON PQEncOn
|
||||
|
||||
pattern MsgErr :: AgentMsgId -> MsgErrorType -> MsgBody -> ACommand 'Agent e
|
||||
pattern Msg :: MsgBody -> ACommand 'Agent e
|
||||
pattern Msg msgBody <- MSG MsgMeta {integrity = MsgOk, pqEncryption = PQEncOn} _ msgBody
|
||||
|
||||
pattern Msg' :: AgentMsgId -> PQEncryption -> MsgBody -> ACommand 'Agent e
|
||||
pattern Msg' aMsgId pqEncryption msgBody <- MSG MsgMeta {integrity = MsgOk, recipient = (aMsgId, _), pqEncryption} _ msgBody
|
||||
|
||||
pattern MsgErr :: AgentMsgId -> MsgErrorType -> MsgBody -> ACommand 'Agent 'AEConn
|
||||
pattern MsgErr msgId err msgBody <- MSG MsgMeta {recipient = (msgId, _), integrity = MsgError err} _ msgBody
|
||||
|
||||
pattern Rcvd :: AgentMsgId -> ACommand 'Agent e
|
||||
pattern Rcvd :: AgentMsgId -> ACommand 'Agent 'AEConn
|
||||
pattern Rcvd agentMsgId <- RCVD MsgMeta {integrity = MsgOk} [MsgReceipt {agentMsgId, msgRcptStatus = MROk}]
|
||||
|
||||
smpCfgVPrev :: ProtocolClientConfig
|
||||
@@ -184,6 +213,18 @@ inAnyOrder g rs = do
|
||||
expected :: a -> (a -> Bool) -> Bool
|
||||
expected r rp = rp r
|
||||
|
||||
createConnection :: AgentErrorMonad m => AgentClient -> UserId -> Bool -> SConnectionMode c -> Maybe CRClientData -> SubscriptionMode -> m (ConnId, ConnectionRequestUri c)
|
||||
createConnection c userId enableNtfs cMode clientData = Agent.createConnection c userId enableNtfs cMode clientData (IKNoPQ PQEncOn)
|
||||
|
||||
joinConnection :: AgentErrorMonad m => AgentClient -> UserId -> Bool -> ConnectionRequestUri c -> ConnInfo -> SubscriptionMode -> m ConnId
|
||||
joinConnection c userId enableNtfs cReq connInfo = Agent.joinConnection c userId enableNtfs cReq connInfo PQEncOn
|
||||
|
||||
sendMessage :: AgentErrorMonad m => AgentClient -> ConnId -> SMP.MsgFlags -> MsgBody -> m AgentMsgId
|
||||
sendMessage c connId msgFlags msgBody = do
|
||||
(msgId, pqEnc) <- Agent.sendMessage c connId PQEncOn msgFlags msgBody
|
||||
liftIO $ pqEnc `shouldBe` PQEncOn
|
||||
pure msgId
|
||||
|
||||
functionalAPITests :: ATransport -> Spec
|
||||
functionalAPITests t = do
|
||||
describe "Establishing duplex connection" $ do
|
||||
@@ -259,9 +300,9 @@ functionalAPITests t = do
|
||||
describe "Batching SMP commands" $ do
|
||||
it "should subscribe to multiple (200) subscriptions with batching" $
|
||||
testBatchedSubscriptions 200 10 t
|
||||
-- 200 subscriptions gets very slow with test coverage, use below test instead
|
||||
xit "should subscribe to multiple (6) subscriptions with batching" $
|
||||
testBatchedSubscriptions 6 3 t
|
||||
skip "faster version of the previous test (200 subscriptions gets very slow with test coverage)" $
|
||||
it "should subscribe to multiple (6) subscriptions with batching" $
|
||||
testBatchedSubscriptions 6 3 t
|
||||
describe "Async agent commands" $ do
|
||||
it "should connect using async agent commands" $
|
||||
withSmpServer t testAsyncCommands
|
||||
@@ -381,20 +422,22 @@ testMatrix2 t runTest = do
|
||||
it "current to v7" $ withSmpServerV7 t $ runTestCfg2 agentCfg agentCfgV7 3 runTest
|
||||
it "current with v7 server" $ withSmpServerV7 t $ runTestCfg2 agentCfg agentCfg 3 runTest
|
||||
it "current" $ withSmpServer t $ runTestCfg2 agentCfg agentCfg 3 runTest
|
||||
it "prev" $ withSmpServer t $ runTestCfg2 agentCfgVPrev agentCfgVPrev 3 runTest
|
||||
it "prev to current" $ withSmpServer t $ runTestCfg2 agentCfgVPrev agentCfg 3 runTest
|
||||
it "current to prev" $ withSmpServer t $ runTestCfg2 agentCfg agentCfgVPrev 3 runTest
|
||||
skip "TODO PQ versioning" $ describe "TODO fails with previous version" $ do
|
||||
it "prev" $ withSmpServer t $ runTestCfg2 agentCfgVPrev agentCfgVPrev 3 runTest
|
||||
it "prev to current" $ withSmpServer t $ runTestCfg2 agentCfgVPrev agentCfg 3 runTest
|
||||
it "current to prev" $ withSmpServer t $ runTestCfg2 agentCfg agentCfgVPrev 3 runTest
|
||||
|
||||
testRatchetMatrix2 :: ATransport -> (AgentClient -> AgentClient -> AgentMsgId -> IO ()) -> Spec
|
||||
testRatchetMatrix2 t runTest = do
|
||||
it "ratchet current" $ withSmpServer t $ runTestCfg2 agentCfg agentCfg 3 runTest
|
||||
pendingV "ratchet prev" $ withSmpServer t $ runTestCfg2 agentCfgRatchetVPrev agentCfgRatchetVPrev 3 runTest
|
||||
pendingV "ratchets prev to current" $ withSmpServer t $ runTestCfg2 agentCfgRatchetVPrev agentCfg 3 runTest
|
||||
pendingV "ratchets current to prev" $ withSmpServer t $ runTestCfg2 agentCfg agentCfgRatchetVPrev 3 runTest
|
||||
skip "TODO PQ versioning" $ describe "TODO fails with previous version" $ do
|
||||
pendingV "ratchet prev" $ withSmpServer t $ runTestCfg2 agentCfgRatchetVPrev agentCfgRatchetVPrev 3 runTest
|
||||
pendingV "ratchets prev to current" $ withSmpServer t $ runTestCfg2 agentCfgRatchetVPrev agentCfg 3 runTest
|
||||
pendingV "ratchets current to prev" $ withSmpServer t $ runTestCfg2 agentCfg agentCfgRatchetVPrev 3 runTest
|
||||
where
|
||||
pendingV =
|
||||
pendingV d =
|
||||
let vr = e2eEncryptVRange agentCfg
|
||||
in if minVersion vr == maxVersion vr then xit else it
|
||||
in if minVersion vr == maxVersion vr then skip "previous version is not supported" . it d else it d
|
||||
|
||||
testServerMatrix2 :: ATransport -> (InitialAgentServers -> IO ()) -> Spec
|
||||
testServerMatrix2 t runTest = do
|
||||
@@ -483,7 +526,7 @@ runAgentClientContactTest alice bob baseId =
|
||||
(_, qInfo) <- createConnection alice 1 True SCMContact Nothing SMSubscribe
|
||||
aliceId <- joinConnection bob 1 True qInfo "bob's connInfo" SMSubscribe
|
||||
("", _, REQ invId _ "bob's connInfo") <- get alice
|
||||
bobId <- acceptContact alice True invId "alice's connInfo" SMSubscribe
|
||||
bobId <- acceptContact alice True invId "alice's connInfo" PQEncOn SMSubscribe
|
||||
("", _, CONF confId _ "alice's connInfo") <- get bob
|
||||
allowConnection bob aliceId confId "bob's connInfo"
|
||||
get alice ##> ("", bobId, INFO "bob's connInfo")
|
||||
@@ -979,7 +1022,7 @@ testRatchetSync t = withAgentClients2 $ \alice bob ->
|
||||
withSmpServerStoreMsgLogOn t testPort $ \_ -> do
|
||||
(aliceId, bobId, bob2) <- setupDesynchronizedRatchet alice bob
|
||||
runRight $ do
|
||||
ConnectionStats {ratchetSyncState} <- synchronizeRatchet bob2 aliceId False
|
||||
ConnectionStats {ratchetSyncState} <- synchronizeRatchet bob2 aliceId PQEncOn False
|
||||
liftIO $ ratchetSyncState `shouldBe` RSStarted
|
||||
get alice =##> ratchetSyncP bobId RSAgreed
|
||||
get bob2 =##> ratchetSyncP aliceId RSAgreed
|
||||
@@ -1023,7 +1066,7 @@ setupDesynchronizedRatchet alice bob = do
|
||||
runRight_ $ do
|
||||
subscribeConnection bob2 aliceId
|
||||
|
||||
Left Agent.CMD {cmdErr = PROHIBITED} <- runExceptT $ synchronizeRatchet bob2 aliceId False
|
||||
Left Agent.CMD {cmdErr = PROHIBITED} <- runExceptT $ synchronizeRatchet bob2 aliceId PQEncOn False
|
||||
|
||||
8 <- sendMessage alice bobId SMP.noMsgFlags "hello 5"
|
||||
get alice ##> ("", bobId, SENT 8)
|
||||
@@ -1054,7 +1097,7 @@ testRatchetSyncServerOffline t = withAgentClients2 $ \alice bob -> do
|
||||
("", "", DOWN _ _) <- nGet alice
|
||||
("", "", DOWN _ _) <- nGet bob2
|
||||
|
||||
ConnectionStats {ratchetSyncState} <- runRight $ synchronizeRatchet bob2 aliceId False
|
||||
ConnectionStats {ratchetSyncState} <- runRight $ synchronizeRatchet bob2 aliceId PQEncOn False
|
||||
liftIO $ ratchetSyncState `shouldBe` RSStarted
|
||||
|
||||
withSmpServerStoreMsgLogOn t testPort $ \_ -> do
|
||||
@@ -1084,7 +1127,7 @@ testRatchetSyncClientRestart t = do
|
||||
setupDesynchronizedRatchet alice bob
|
||||
("", "", DOWN _ _) <- nGet alice
|
||||
("", "", DOWN _ _) <- nGet bob2
|
||||
ConnectionStats {ratchetSyncState} <- runRight $ synchronizeRatchet bob2 aliceId False
|
||||
ConnectionStats {ratchetSyncState} <- runRight $ synchronizeRatchet bob2 aliceId PQEncOn False
|
||||
liftIO $ ratchetSyncState `shouldBe` RSStarted
|
||||
disconnectAgentClient bob2
|
||||
bob3 <- getSMPAgentClient' 3 agentCfg initAgentServers testDB2
|
||||
@@ -1111,7 +1154,7 @@ testRatchetSyncSuspendForeground t = do
|
||||
("", "", DOWN _ _) <- nGet alice
|
||||
("", "", DOWN _ _) <- nGet bob2
|
||||
|
||||
ConnectionStats {ratchetSyncState} <- runRight $ synchronizeRatchet bob2 aliceId False
|
||||
ConnectionStats {ratchetSyncState} <- runRight $ synchronizeRatchet bob2 aliceId PQEncOn False
|
||||
liftIO $ ratchetSyncState `shouldBe` RSStarted
|
||||
|
||||
suspendAgent bob2 0
|
||||
@@ -1145,10 +1188,10 @@ testRatchetSyncSimultaneous t = do
|
||||
("", "", DOWN _ _) <- nGet alice
|
||||
("", "", DOWN _ _) <- nGet bob2
|
||||
|
||||
ConnectionStats {ratchetSyncState = bRSS} <- runRight $ synchronizeRatchet bob2 aliceId False
|
||||
ConnectionStats {ratchetSyncState = bRSS} <- runRight $ synchronizeRatchet bob2 aliceId PQEncOn False
|
||||
liftIO $ bRSS `shouldBe` RSStarted
|
||||
|
||||
ConnectionStats {ratchetSyncState = aRSS} <- runRight $ synchronizeRatchet alice bobId True
|
||||
ConnectionStats {ratchetSyncState = aRSS} <- runRight $ synchronizeRatchet alice bobId PQEncOn True
|
||||
liftIO $ aRSS `shouldBe` RSStarted
|
||||
|
||||
withSmpServerStoreMsgLogOn t testPort $ \_ -> do
|
||||
@@ -1203,17 +1246,23 @@ testOnlyCreatePull = withAgentClients2 $ \alice bob -> runRight_ $ do
|
||||
pure r
|
||||
|
||||
makeConnection :: AgentClient -> AgentClient -> ExceptT AgentErrorType IO (ConnId, ConnId)
|
||||
makeConnection alice bob = makeConnectionForUsers alice 1 bob 1
|
||||
makeConnection = makeConnection_ PQEncOn
|
||||
|
||||
makeConnection_ :: PQEncryption -> AgentClient -> AgentClient -> ExceptT AgentErrorType IO (ConnId, ConnId)
|
||||
makeConnection_ pqEnc alice bob = makeConnectionForUsers_ pqEnc alice 1 bob 1
|
||||
|
||||
makeConnectionForUsers :: AgentClient -> UserId -> AgentClient -> UserId -> ExceptT AgentErrorType IO (ConnId, ConnId)
|
||||
makeConnectionForUsers alice aliceUserId bob bobUserId = do
|
||||
(bobId, qInfo) <- createConnection alice aliceUserId True SCMInvitation Nothing SMSubscribe
|
||||
aliceId <- joinConnection bob bobUserId True qInfo "bob's connInfo" SMSubscribe
|
||||
makeConnectionForUsers = makeConnectionForUsers_ PQEncOn
|
||||
|
||||
makeConnectionForUsers_ :: PQEncryption -> AgentClient -> UserId -> AgentClient -> UserId -> ExceptT AgentErrorType IO (ConnId, ConnId)
|
||||
makeConnectionForUsers_ pqEnc alice aliceUserId bob bobUserId = do
|
||||
(bobId, qInfo) <- Agent.createConnection alice aliceUserId True SCMInvitation Nothing (CR.IKNoPQ pqEnc) SMSubscribe
|
||||
aliceId <- Agent.joinConnection bob bobUserId True qInfo "bob's connInfo" pqEnc SMSubscribe
|
||||
("", _, CONF confId _ "bob's connInfo") <- get alice
|
||||
allowConnection alice bobId confId "alice's connInfo"
|
||||
get alice ##> ("", bobId, CON)
|
||||
get alice ##> ("", bobId, Agent.CON pqEnc)
|
||||
get bob ##> ("", aliceId, INFO "alice's connInfo")
|
||||
get bob ##> ("", aliceId, CON)
|
||||
get bob ##> ("", aliceId, Agent.CON pqEnc)
|
||||
pure (aliceId, bobId)
|
||||
|
||||
testInactiveNoSubs :: ATransport -> IO ()
|
||||
@@ -1336,8 +1385,8 @@ testBatchedSubscriptions nCreate nDel t = do
|
||||
a <- getSMPAgentClient' 1 agentCfg initAgentServers2 testDB
|
||||
b <- getSMPAgentClient' 2 agentCfg initAgentServers2 testDB2
|
||||
conns <- runServers $ do
|
||||
conns <- replicateM (nCreate :: Int) $ makeConnection a b
|
||||
forM_ conns $ \(aId, bId) -> exchangeGreetings a bId b aId
|
||||
conns <- replicateM (nCreate :: Int) $ makeConnection_ PQEncOff a b
|
||||
forM_ conns $ \(aId, bId) -> exchangeGreetings_ PQEncOff a bId b aId
|
||||
let (aIds', bIds') = unzip $ take nDel conns
|
||||
delete a bIds'
|
||||
delete b aIds'
|
||||
@@ -1358,10 +1407,10 @@ testBatchedSubscriptions nCreate nDel t = do
|
||||
(aIds', bIds') = unzip conns'
|
||||
subscribe a bIds
|
||||
subscribe b aIds
|
||||
forM_ conns' $ \(aId, bId) -> exchangeGreetingsMsgId 6 a bId b aId
|
||||
forM_ conns' $ \(aId, bId) -> exchangeGreetingsMsgId_ PQEncOff 6 a bId b aId
|
||||
void $ resubscribeConnections a bIds
|
||||
void $ resubscribeConnections b aIds
|
||||
forM_ conns' $ \(aId, bId) -> exchangeGreetingsMsgId 8 a bId b aId
|
||||
forM_ conns' $ \(aId, bId) -> exchangeGreetingsMsgId_ PQEncOff 8 a bId b aId
|
||||
delete a bIds'
|
||||
delete b aIds'
|
||||
deleteFail a bIds'
|
||||
@@ -1400,10 +1449,10 @@ testBatchedSubscriptions nCreate nDel t = do
|
||||
testAsyncCommands :: IO ()
|
||||
testAsyncCommands =
|
||||
withAgentClients2 $ \alice bob -> runRight_ $ do
|
||||
bobId <- createConnectionAsync alice 1 "1" True SCMInvitation SMSubscribe
|
||||
bobId <- createConnectionAsync alice 1 "1" True SCMInvitation (IKNoPQ PQEncOn) SMSubscribe
|
||||
("1", bobId', INV (ACR _ qInfo)) <- get alice
|
||||
liftIO $ bobId' `shouldBe` bobId
|
||||
aliceId <- joinConnectionAsync bob 1 "2" True qInfo "bob's connInfo" SMSubscribe
|
||||
aliceId <- joinConnectionAsync bob 1 "2" True qInfo "bob's connInfo" PQEncOn SMSubscribe
|
||||
("2", aliceId', OK) <- get bob
|
||||
liftIO $ aliceId' `shouldBe` aliceId
|
||||
("", _, CONF confId _ "bob's connInfo") <- get alice
|
||||
@@ -1450,7 +1499,7 @@ testAsyncCommands =
|
||||
testAsyncCommandsRestore :: ATransport -> IO ()
|
||||
testAsyncCommandsRestore t = do
|
||||
alice <- getSMPAgentClient' 1 agentCfg initAgentServers testDB
|
||||
bobId <- runRight $ createConnectionAsync alice 1 "1" True SCMInvitation SMSubscribe
|
||||
bobId <- runRight $ createConnectionAsync alice 1 "1" True SCMInvitation (IKNoPQ PQEncOn) SMSubscribe
|
||||
liftIO $ noMessages alice "alice doesn't receive INV because server is down"
|
||||
disconnectAgentClient alice
|
||||
alice' <- liftIO $ getSMPAgentClient' 2 agentCfg initAgentServers testDB
|
||||
@@ -1467,7 +1516,7 @@ testAcceptContactAsync =
|
||||
(_, qInfo) <- createConnection alice 1 True SCMContact Nothing SMSubscribe
|
||||
aliceId <- joinConnection bob 1 True qInfo "bob's connInfo" SMSubscribe
|
||||
("", _, REQ invId _ "bob's connInfo") <- get alice
|
||||
bobId <- acceptContactAsync alice "1" True invId "alice's connInfo" SMSubscribe
|
||||
bobId <- acceptContactAsync alice "1" True invId "alice's connInfo" PQEncOn SMSubscribe
|
||||
get alice =##> \case ("1", c, OK) -> c == bobId; _ -> False
|
||||
("", _, CONF confId _ "alice's connInfo") <- get bob
|
||||
allowConnection bob aliceId confId "bob's connInfo"
|
||||
@@ -1685,6 +1734,8 @@ testWaitDeliveryTimeout t = do
|
||||
liftIO $ noMessages alice "nothing else should be delivered to alice"
|
||||
liftIO $ noMessages bob "nothing else should be delivered to bob"
|
||||
|
||||
liftIO $ threadDelay 100000
|
||||
|
||||
withSmpServerStoreLogOn t testPort $ \_ -> do
|
||||
nGet bob =##> \case ("", "", UP _ [cId]) -> cId == aliceId; _ -> False
|
||||
liftIO $ noMessages alice "nothing else should be delivered to alice"
|
||||
@@ -1751,10 +1802,10 @@ testJoinConnectionAsyncReplyError t = do
|
||||
a <- getSMPAgentClient' 1 agentCfg initAgentServers testDB
|
||||
b <- getSMPAgentClient' 2 agentCfg initAgentServersSrv2 testDB2
|
||||
(aId, bId) <- withSmpServerStoreLogOn t testPort $ \_ -> runRight $ do
|
||||
bId <- createConnectionAsync a 1 "1" True SCMInvitation SMSubscribe
|
||||
bId <- createConnectionAsync a 1 "1" True SCMInvitation (IKNoPQ PQEncOn) SMSubscribe
|
||||
("1", bId', INV (ACR _ qInfo)) <- get a
|
||||
liftIO $ bId' `shouldBe` bId
|
||||
aId <- joinConnectionAsync b 1 "2" True qInfo "bob's connInfo" SMSubscribe
|
||||
aId <- joinConnectionAsync b 1 "2" True qInfo "bob's connInfo" PQEncOn SMSubscribe
|
||||
liftIO $ threadDelay 500000
|
||||
ConnectionStats {rcvQueuesInfo = [], sndQueuesInfo = [SndQueueInfo {}]} <- getConnectionServers b aId
|
||||
pure (aId, bId)
|
||||
@@ -2344,7 +2395,7 @@ testDeliveryReceiptsConcurrent t =
|
||||
t1 <- liftIO getCurrentTime
|
||||
concurrently_ (runClient "a" a bId) (runClient "b" b aId)
|
||||
t2 <- liftIO getCurrentTime
|
||||
diffUTCTime t2 t1 `shouldSatisfy` (< 15)
|
||||
diffUTCTime t2 t1 `shouldSatisfy` (< 60)
|
||||
liftIO $ noMessages a "nothing else should be delivered to alice"
|
||||
liftIO $ noMessages b "nothing else should be delivered to bob"
|
||||
where
|
||||
@@ -2355,7 +2406,6 @@ testDeliveryReceiptsConcurrent t =
|
||||
numMsgs = 100
|
||||
send = runRight_ $
|
||||
replicateM_ numMsgs $ do
|
||||
-- liftIO $ print $ cName <> ": sendMessage"
|
||||
void $ sendMessage client connId SMP.noMsgFlags "hello"
|
||||
receive =
|
||||
runRight_ $
|
||||
@@ -2383,7 +2433,7 @@ testDeliveryReceiptsConcurrent t =
|
||||
receiveLoop (n - 1)
|
||||
getWithTimeout :: ExceptT AgentErrorType IO (AEntityTransmission 'AEConn)
|
||||
getWithTimeout = do
|
||||
1000000 `timeout` get client >>= \case
|
||||
3000000 `timeout` get client >>= \case
|
||||
Just r -> pure r
|
||||
_ -> error "timeout"
|
||||
|
||||
@@ -2493,20 +2543,26 @@ testServerMultipleIdentities =
|
||||
testE2ERatchetParams12
|
||||
|
||||
exchangeGreetings :: HasCallStack => AgentClient -> ConnId -> AgentClient -> ConnId -> ExceptT AgentErrorType IO ()
|
||||
exchangeGreetings = exchangeGreetingsMsgId 4
|
||||
exchangeGreetings = exchangeGreetings_ PQEncOn
|
||||
|
||||
exchangeGreetings_ :: HasCallStack => PQEncryption -> AgentClient -> ConnId -> AgentClient -> ConnId -> ExceptT AgentErrorType IO ()
|
||||
exchangeGreetings_ pqEnc = exchangeGreetingsMsgId_ pqEnc 4
|
||||
|
||||
exchangeGreetingsMsgId :: HasCallStack => Int64 -> AgentClient -> ConnId -> AgentClient -> ConnId -> ExceptT AgentErrorType IO ()
|
||||
exchangeGreetingsMsgId msgId alice bobId bob aliceId = do
|
||||
msgId1 <- sendMessage alice bobId SMP.noMsgFlags "hello"
|
||||
liftIO $ msgId1 `shouldBe` msgId
|
||||
exchangeGreetingsMsgId = exchangeGreetingsMsgId_ PQEncOn
|
||||
|
||||
exchangeGreetingsMsgId_ :: HasCallStack => PQEncryption -> Int64 -> AgentClient -> ConnId -> AgentClient -> ConnId -> ExceptT AgentErrorType IO ()
|
||||
exchangeGreetingsMsgId_ pqEnc msgId alice bobId bob aliceId = do
|
||||
msgId1 <- Agent.sendMessage alice bobId pqEnc SMP.noMsgFlags "hello"
|
||||
liftIO $ msgId1 `shouldBe` (msgId, pqEnc)
|
||||
get alice ##> ("", bobId, SENT msgId)
|
||||
get bob =##> \case ("", c, Msg "hello") -> c == aliceId; _ -> False
|
||||
get bob =##> \case ("", c, Msg' mId pq "hello") -> c == aliceId && mId == msgId && pq == pqEnc; _ -> False
|
||||
ackMessage bob aliceId msgId Nothing
|
||||
msgId2 <- sendMessage bob aliceId SMP.noMsgFlags "hello too"
|
||||
msgId2 <- Agent.sendMessage bob aliceId pqEnc SMP.noMsgFlags "hello too"
|
||||
let msgId' = msgId + 1
|
||||
liftIO $ msgId2 `shouldBe` msgId'
|
||||
liftIO $ msgId2 `shouldBe` (msgId', pqEnc)
|
||||
get bob ##> ("", aliceId, SENT msgId')
|
||||
get alice =##> \case ("", c, Msg "hello too") -> c == bobId; _ -> False
|
||||
get alice =##> \case ("", c, Msg' mId pq "hello too") -> c == bobId && mId == msgId' && pq == pqEnc; _ -> False
|
||||
ackMessage alice bobId msgId' Nothing
|
||||
|
||||
exchangeGreetingsMsgIds :: HasCallStack => AgentClient -> ConnId -> Int64 -> AgentClient -> ConnId -> Int64 -> ExceptT AgentErrorType IO ()
|
||||
|
||||
@@ -12,7 +12,26 @@
|
||||
module AgentTests.NotificationTests where
|
||||
|
||||
-- import Control.Logger.Simple (LogConfig (..), LogLevel (..), setLogLevel, withGlobalLogging)
|
||||
import AgentTests.FunctionalAPITests (agentCfgV7, exchangeGreetingsMsgId, get, getSMPAgentClient', makeConnection, nGet, runRight, runRight_, switchComplete, testServerMatrix2, withAgentClientsCfg2, (##>), (=##>), pattern Msg)
|
||||
import AgentTests.FunctionalAPITests
|
||||
( agentCfgV7,
|
||||
createConnection,
|
||||
exchangeGreetingsMsgId,
|
||||
get,
|
||||
getSMPAgentClient',
|
||||
joinConnection,
|
||||
makeConnection,
|
||||
nGet,
|
||||
runRight,
|
||||
runRight_,
|
||||
sendMessage,
|
||||
switchComplete,
|
||||
testServerMatrix2,
|
||||
withAgentClientsCfg2,
|
||||
(##>),
|
||||
(=##>),
|
||||
pattern CON,
|
||||
pattern Msg,
|
||||
)
|
||||
import Control.Concurrent (ThreadId, killThread, threadDelay)
|
||||
import Control.Monad
|
||||
import Control.Monad.Except
|
||||
@@ -28,10 +47,10 @@ import Data.Text.Encoding (encodeUtf8)
|
||||
import NtfClient
|
||||
import SMPAgentClient (agentCfg, initAgentServers, initAgentServers2, testDB, testDB2, testDB3, testNtfServer, testNtfServer2)
|
||||
import SMPClient (cfg, cfgV7, testPort, testPort2, testStoreLogFile2, withSmpServer, withSmpServerConfigOn, withSmpServerStoreLogOn)
|
||||
import Simplex.Messaging.Agent
|
||||
import Simplex.Messaging.Agent hiding (createConnection, joinConnection, sendMessage)
|
||||
import Simplex.Messaging.Agent.Client (ProtocolTestFailure (..), ProtocolTestStep (..), withStore')
|
||||
import Simplex.Messaging.Agent.Env.SQLite (AgentConfig, Env (..), InitialAgentServers)
|
||||
import Simplex.Messaging.Agent.Protocol
|
||||
import Simplex.Messaging.Agent.Protocol hiding (CON)
|
||||
import Simplex.Messaging.Agent.Store.SQLite (getSavedNtfToken)
|
||||
import qualified Simplex.Messaging.Crypto as C
|
||||
import Simplex.Messaging.Encoding.String
|
||||
@@ -46,6 +65,7 @@ import Simplex.Messaging.Transport (ATransport)
|
||||
import System.Directory (doesFileExist, removeFile)
|
||||
import Test.Hspec
|
||||
import UnliftIO
|
||||
import Util
|
||||
|
||||
removeFileIfExists :: FilePath -> IO ()
|
||||
removeFileIfExists filePath = do
|
||||
@@ -125,8 +145,8 @@ testNtfMatrix t runTest = do
|
||||
it "next servers: SMP v7, NTF v2; next clients: v7/v2" $ runNtfTestCfg t cfgV7 ntfServerCfgV2 agentCfgV7 agentCfgV7 runTest
|
||||
it "next servers: SMP v7, NTF v2; curr clients: v6/v1" $ runNtfTestCfg t cfgV7 ntfServerCfgV2 agentCfg agentCfg runTest
|
||||
it "curr servers: SMP v6, NTF v1; curr clients: v6/v1" $ runNtfTestCfg t cfg ntfServerCfg agentCfg agentCfg runTest
|
||||
-- this case will cannot be supported - see RFC
|
||||
xit "servers: SMP v6, NTF v1; clients: v7/v2 (not supported)" $ runNtfTestCfg t cfg ntfServerCfg agentCfgV7 agentCfgV7 runTest
|
||||
skip "this case cannot be supported - see RFC" $
|
||||
it "servers: SMP v6, NTF v1; clients: v7/v2 (not supported)" $ runNtfTestCfg t cfg ntfServerCfg agentCfgV7 agentCfgV7 runTest
|
||||
-- servers can be migrated in any order
|
||||
it "servers: next SMP v7, curr NTF v1; curr clients: v6/v1" $ runNtfTestCfg t cfgV7 ntfServerCfg agentCfg agentCfg runTest
|
||||
it "servers: curr SMP v6, next NTF v2; curr clients: v6/v1" $ runNtfTestCfg t cfg ntfServerCfgV2 agentCfg agentCfg runTest
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
{-# LANGUAGE DataKinds #-}
|
||||
{-# LANGUAGE DuplicateRecordFields #-}
|
||||
{-# LANGUAGE GADTs #-}
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
{-# LANGUAGE NamedFieldPuns #-}
|
||||
{-# LANGUAGE OverloadedLists #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
{-# LANGUAGE PatternSynonyms #-}
|
||||
{-# LANGUAGE RecordWildCards #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# LANGUAGE StandaloneDeriving #-}
|
||||
{-# OPTIONS_GHC -Wno-orphans #-}
|
||||
{-# OPTIONS_GHC -fno-warn-ambiguous-fields #-}
|
||||
|
||||
module AgentTests.SQLiteTests (storeTests) where
|
||||
|
||||
import AgentTests.EqInstances ()
|
||||
import Control.Concurrent.Async (concurrently_)
|
||||
import Control.Concurrent.STM
|
||||
import Control.Exception (SomeException)
|
||||
@@ -40,6 +45,8 @@ import Simplex.Messaging.Agent.Store.SQLite.Common (withTransaction')
|
||||
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB
|
||||
import qualified Simplex.Messaging.Agent.Store.SQLite.Migrations as Migrations
|
||||
import qualified Simplex.Messaging.Crypto as C
|
||||
import Simplex.Messaging.Crypto.Ratchet (InitialKeys (..), pattern PQEncOn)
|
||||
import qualified Simplex.Messaging.Crypto.Ratchet as CR
|
||||
import Simplex.Messaging.Crypto.File (CryptoFile (..))
|
||||
import Simplex.Messaging.Encoding.String (StrEncoding (..))
|
||||
import Simplex.Messaging.Protocol (SubscriptionMode (..))
|
||||
@@ -174,7 +181,17 @@ testForeignKeysEnabled =
|
||||
`shouldThrow` (\e -> SQL.sqlError e == SQL.ErrorConstraint)
|
||||
|
||||
cData1 :: ConnData
|
||||
cData1 = ConnData {userId = 1, connId = "conn1", connAgentVersion = 1, enableNtfs = True, lastExternalSndId = 0, deleted = False, ratchetSyncState = RSOk}
|
||||
cData1 =
|
||||
ConnData
|
||||
{ userId = 1,
|
||||
connId = "conn1",
|
||||
connAgentVersion = 1,
|
||||
enableNtfs = True,
|
||||
lastExternalSndId = 0,
|
||||
deleted = False,
|
||||
ratchetSyncState = RSOk,
|
||||
pqEncryption = CR.PQEncOn
|
||||
}
|
||||
|
||||
testPrivateAuthKey :: C.APrivateAuthKey
|
||||
testPrivateAuthKey = C.APrivateAuthKey C.SEd25519 "MC4CAQAwBQYDK2VwBCIEIDfEfevydXXfKajz3sRkcQ7RPvfWUPoq6pu1TYHV1DEe"
|
||||
@@ -467,7 +484,8 @@ mkRcvMsgData internalId internalRcvId externalSndId brokerId internalHash =
|
||||
{ integrity = MsgOk,
|
||||
recipient = (unId internalId, ts),
|
||||
sndMsgId = externalSndId,
|
||||
broker = (brokerId, ts)
|
||||
broker = (brokerId, ts),
|
||||
pqEncryption = CR.PQEncOn
|
||||
},
|
||||
msgType = AM_A_MSG_,
|
||||
msgFlags = SMP.noMsgFlags,
|
||||
@@ -505,6 +523,7 @@ mkSndMsgData internalId internalSndId internalHash =
|
||||
msgType = AM_A_MSG_,
|
||||
msgFlags = SMP.noMsgFlags,
|
||||
msgBody = hw,
|
||||
pqEncryption = CR.PQEncOn,
|
||||
internalHash,
|
||||
prevMsgHash = internalHash
|
||||
}
|
||||
@@ -643,7 +662,7 @@ testGetPendingServerCommand st = do
|
||||
Right (Just PendingCommand {corrId = corrId'}) <- getPendingServerCommand db (Just smpServer1)
|
||||
corrId' `shouldBe` "4"
|
||||
where
|
||||
command = AClientCommand $ APC SAEConn $ NEW True (ACM SCMInvitation) SMSubscribe
|
||||
command = AClientCommand $ APC SAEConn $ NEW True (ACM SCMInvitation) (IKNoPQ PQEncOn) SMSubscribe
|
||||
corruptCmd :: DB.Connection -> ByteString -> ConnId -> IO ()
|
||||
corruptCmd db corrId connId = DB.execute db "UPDATE commands SET command = cast('bad' as blob) WHERE conn_id = ? AND corr_id = ?" (connId, corrId)
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
{-# LANGUAGE GADTs #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# OPTIONS_GHC -Wno-orphans #-}
|
||||
|
||||
module CoreTests.CryptoTests (cryptoTests) where
|
||||
|
||||
@@ -13,6 +15,7 @@ import qualified Data.Text as T
|
||||
import Data.Text.Encoding (encodeUtf8)
|
||||
import qualified Data.Text.Lazy as LT
|
||||
import qualified Data.Text.Lazy.Encoding as LE
|
||||
import Data.Type.Equality
|
||||
import qualified Simplex.Messaging.Crypto as C
|
||||
import qualified Simplex.Messaging.Crypto.Lazy as LC
|
||||
import Simplex.Messaging.Crypto.SNTRUP761.Bindings
|
||||
@@ -91,6 +94,16 @@ cryptoTests = do
|
||||
describe "sntrup761" $
|
||||
it "should enc/dec key" testSNTRUP761
|
||||
|
||||
instance Eq C.APublicKey where
|
||||
C.APublicKey a k == C.APublicKey a' k' = case testEquality a a' of
|
||||
Just Refl -> k == k'
|
||||
Nothing -> False
|
||||
|
||||
instance Eq C.APrivateKey where
|
||||
C.APrivateKey a k == C.APrivateKey a' k' = case testEquality a a' of
|
||||
Just Refl -> k == k'
|
||||
Nothing -> False
|
||||
|
||||
testPadUnpadFile :: IO ()
|
||||
testPadUnpadFile = do
|
||||
let f = "tests/tmp/testpad"
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
module CoreTests.TRcvQueuesTests where
|
||||
|
||||
import AgentTests.EqInstances ()
|
||||
import qualified Data.List.NonEmpty as L
|
||||
import qualified Data.Map as M
|
||||
import qualified Data.Set as S
|
||||
|
||||
+3
-2
@@ -34,6 +34,7 @@ import UnliftIO.Concurrent
|
||||
import qualified UnliftIO.Exception as E
|
||||
import UnliftIO.STM (TMVar, atomically, newEmptyTMVarIO, takeTMVar)
|
||||
import UnliftIO.Timeout (timeout)
|
||||
import Util
|
||||
|
||||
testHost :: NonEmpty TransportHost
|
||||
testHost = "localhost"
|
||||
@@ -60,12 +61,12 @@ testServerStatsBackupFile :: FilePath
|
||||
testServerStatsBackupFile = "tests/tmp/smp-server-stats.log"
|
||||
|
||||
xit' :: (HasCallStack, Example a) => String -> a -> SpecWith (Arg a)
|
||||
xit' = if os == "linux" then xit else it
|
||||
xit' d = if os == "linux" then skip "skipped on Linux" . it d else it d
|
||||
|
||||
xit'' :: (HasCallStack, Example a) => String -> a -> SpecWith (Arg a)
|
||||
xit'' d t = do
|
||||
ci <- runIO $ lookupEnv "CI"
|
||||
(if ci == Just "true" then xit else it) d t
|
||||
(if ci == Just "true" then skip "skipped on CI" . it d else it d) t
|
||||
|
||||
testSMPClient :: (Transport c, MonadUnliftIO m, MonadFail m) => (THandle c -> m a) -> m a
|
||||
testSMPClient = testSMPClientVR supportedClientSMPRelayVRange
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE PatternSynonyms #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# LANGUAGE StandaloneDeriving #-}
|
||||
{-# LANGUAGE TypeApplications #-}
|
||||
{-# OPTIONS_GHC -Wno-orphans #-}
|
||||
|
||||
module ServerTests where
|
||||
|
||||
@@ -23,6 +25,7 @@ import Data.ByteString.Base64
|
||||
import Data.ByteString.Char8 (ByteString)
|
||||
import qualified Data.ByteString.Char8 as B
|
||||
import qualified Data.Set as S
|
||||
import Data.Type.Equality
|
||||
import GHC.Stack (withFrozenCallStack)
|
||||
import SMPClient
|
||||
import qualified Simplex.Messaging.Crypto as C
|
||||
@@ -919,6 +922,15 @@ sampleSig = Just $ TASignature "e8JK+8V3fq6kOLqco/SaKlpNaQ7i1gfOrXoqekEl42u4mF8B
|
||||
noAuth :: (Char, Maybe BasicAuth)
|
||||
noAuth = ('A', Nothing)
|
||||
|
||||
deriving instance Eq TransmissionAuth
|
||||
|
||||
instance Eq C.ASignature where
|
||||
C.ASignature a s == C.ASignature a' s' = case testEquality a a' of
|
||||
Just Refl -> s == s'
|
||||
_ -> False
|
||||
|
||||
deriving instance Eq (C.Signature a)
|
||||
|
||||
syntaxTests :: ATransport -> Spec
|
||||
syntaxTests (ATransport t) = do
|
||||
it "unknown command" $ ("", "abcd", "1234", ('H', 'E', 'L', 'L', 'O')) >#> ("", "abcd", "1234", ERR $ CMD UNKNOWN)
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
module Util where
|
||||
|
||||
import Test.Hspec
|
||||
|
||||
skip :: String -> SpecWith a -> SpecWith a
|
||||
skip = before_ . pendingWith
|
||||
Reference in new issue
Block a user