From 851ed2d02e2a78c15893ad8bc9c5a4d917eb6a35 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sun, 10 Mar 2024 13:29:03 +0000 Subject: [PATCH] pqdr: more tests, pass e2e version to rcEncrypt from config (#1040) * pqdr: more tests, pass e2e version to rcEncrypt from config * fix --- src/Simplex/Messaging/Agent.hs | 60 ++++++++++++++----------- src/Simplex/Messaging/Agent/Protocol.hs | 2 + src/Simplex/Messaging/Crypto/Ratchet.hs | 23 ++++------ tests/AgentTests/DoubleRatchetTests.hs | 4 +- tests/AgentTests/FunctionalAPITests.hs | 23 +++++----- 5 files changed, 59 insertions(+), 53 deletions(-) diff --git a/src/Simplex/Messaging/Agent.hs b/src/Simplex/Messaging/Agent.hs index 08678140e..56432b947 100644 --- a/src/Simplex/Messaging/Agent.hs +++ b/src/Simplex/Messaging/Agent.hs @@ -1143,24 +1143,26 @@ enqueueMessage c cData sq msgFlags aMessage = -- this function is used only for sending messages in batch, it returns the list of successes to enqueue additional deliveries enqueueMessageB :: forall m t. (AgentMonad' m, Traversable t) => AgentClient -> t (Either AgentErrorType (ConnData, NonEmpty SndQueue, Maybe PQEncryption, MsgFlags, AMessage)) -> m (t (Either AgentErrorType ((AgentMsgId, PQEncryption), Maybe (ConnData, [SndQueue], AgentMsgId)))) enqueueMessageB c reqs = do - getAVRange <- asks $ smpAgentVRange . config - reqMids <- withStoreBatch c $ \db -> fmap (bindRight $ storeSentMsg db getAVRange) reqs + cfg <- asks config + reqMids <- withStoreBatch c $ \db -> fmap (bindRight $ storeSentMsg db cfg) reqs forME reqMids $ \((cData, sq :| sqs, _, _, _), InternalId msgId, pqSecr) -> do submitPendingMsg c cData sq let sqs' = filter isActiveSndQ sqs pure $ Right ((msgId, pqSecr), if null sqs' then Nothing else Just (cData, sqs', msgId)) where - storeSentMsg :: DB.Connection -> (PQSupport -> VersionRangeSMPA) -> (ConnData, NonEmpty SndQueue, Maybe PQEncryption, MsgFlags, AMessage) -> IO (Either AgentErrorType ((ConnData, NonEmpty SndQueue, Maybe PQEncryption, MsgFlags, AMessage), InternalId, PQEncryption)) - storeSentMsg db getAVRange req@(cData@ConnData {connId, pqSupport}, sq :| _, pqEnc_, msgFlags, aMessage) = fmap (first storeError) $ runExceptT $ do + storeSentMsg :: DB.Connection -> AgentConfig -> (ConnData, NonEmpty SndQueue, Maybe PQEncryption, MsgFlags, AMessage) -> IO (Either AgentErrorType ((ConnData, NonEmpty SndQueue, Maybe PQEncryption, MsgFlags, AMessage), InternalId, PQEncryption)) + storeSentMsg db cfg req@(cData@ConnData {connId, pqSupport}, sq :| _, pqEnc_, msgFlags, aMessage) = fmap (first storeError) $ runExceptT $ do + let AgentConfig {smpAgentVRange, e2eEncryptVRange} = cfg internalTs <- liftIO getCurrentTime (internalId, internalSndId, prevMsgHash) <- liftIO $ updateSndIds db connId let privHeader = APrivHeader (unSndId internalSndId) prevMsgHash agentMsg = AgentMessage privHeader aMessage agentMsgStr = smpEncode agentMsg internalHash = C.sha256Hash agentMsgStr - (encAgentMessage, pqEnc) <- agentRatchetEncrypt db cData agentMsgStr e2eEncUserMsgLength pqEnc_ + currentE2EVersion = maxVersion $ e2eEncryptVRange PQSupportOff + (encAgentMessage, pqEnc) <- agentRatchetEncrypt db cData agentMsgStr e2eEncUserMsgLength pqEnc_ currentE2EVersion -- agent version range is determined by the connection suppport of PQ encryption, that is may be enabled when message is sent - let agentVersion = maxVersion $ getAVRange pqSupport + let agentVersion = maxVersion $ smpAgentVRange pqSupport msgBody = smpEncode $ AgentMsgEnvelope {agentVersion, encAgentMessage} msgType = agentMessageType agentMsg msgData = SndMsgData {internalId, internalSndId, internalTs, msgType, msgFlags, msgBody, pqEncryption = pqEnc, internalHash, prevMsgHash} @@ -2488,11 +2490,14 @@ confirmQueue (Compatible agentVersion) c cData@ConnData {connId, pqSupport} sq s withStore' c $ \db -> setSndQueueStatus db sq Confirmed where mkConfirmation :: AgentMessage -> m MsgBody - mkConfirmation aMessage = withStore c $ \db -> runExceptT $ do - void . liftIO $ updateSndIds db connId - let pqEnc = CR.pqSupportToEnc pqSupport - (encConnInfo, _) <- agentRatchetEncrypt db cData (smpEncode aMessage) e2eEncConnInfoLength (Just pqEnc) - pure . smpEncode $ AgentConfirmation {agentVersion, e2eEncryption_, encConnInfo} + mkConfirmation aMessage = do + -- the version to be used when PQSupport is disabled + currentE2EVersion <- asks $ maxVersion . ($ PQSupportOff) . e2eEncryptVRange . config + withStore c $ \db -> runExceptT $ do + void . liftIO $ updateSndIds db connId + let pqEnc = CR.pqSupportToEnc pqSupport + (encConnInfo, _) <- agentRatchetEncrypt db cData (smpEncode aMessage) e2eEncConnInfoLength (Just pqEnc) currentE2EVersion + pure . smpEncode $ AgentConfirmation {agentVersion, e2eEncryption_, encConnInfo} mkAgentConfirmation :: AgentMonad m => AgentClient -> ConnData -> SndQueue -> SMPServerWithAuth -> ConnInfo -> SubscriptionMode -> m AgentMessage mkAgentConfirmation c cData sq srv connInfo subMode = do @@ -2505,18 +2510,21 @@ enqueueConfirmation c cData sq connInfo e2eEncryption_ = do submitPendingMsg c cData sq storeConfirmation :: AgentMonad m => AgentClient -> ConnData -> SndQueue -> Maybe (CR.SndE2ERatchetParams 'C.X448) -> AgentMessage -> m () -storeConfirmation c cData@ConnData {connId, pqSupport, connAgentVersion = v} sq e2eEncryption_ agentMsg = withStore c $ \db -> runExceptT $ do - internalTs <- liftIO getCurrentTime - (internalId, internalSndId, prevMsgHash) <- liftIO $ updateSndIds db connId - let agentMsgStr = smpEncode agentMsg - internalHash = C.sha256Hash agentMsgStr - pqEnc = CR.pqSupportToEnc pqSupport - (encConnInfo, pqEncryption) <- agentRatchetEncrypt db cData agentMsgStr e2eEncConnInfoLength (Just pqEnc) - let msgBody = smpEncode $ AgentConfirmation {agentVersion = v, e2eEncryption_, encConnInfo} - msgType = agentMessageType agentMsg - msgData = SndMsgData {internalId, internalSndId, internalTs, msgType, msgBody, pqEncryption, msgFlags = SMP.MsgFlags {notification = True}, internalHash, prevMsgHash} - liftIO $ createSndMsg db connId msgData - liftIO $ createSndMsgDelivery db connId sq internalId +storeConfirmation c cData@ConnData {connId, pqSupport, connAgentVersion = v} sq e2eEncryption_ agentMsg = do + -- the version to be used when PQSupport is disabled + currentE2EVersion <- asks $ maxVersion . ($ PQSupportOff) . e2eEncryptVRange . config + withStore c $ \db -> runExceptT $ do + internalTs <- liftIO getCurrentTime + (internalId, internalSndId, prevMsgHash) <- liftIO $ updateSndIds db connId + let agentMsgStr = smpEncode agentMsg + internalHash = C.sha256Hash agentMsgStr + pqEnc = CR.pqSupportToEnc pqSupport + (encConnInfo, pqEncryption) <- agentRatchetEncrypt db cData agentMsgStr e2eEncConnInfoLength (Just pqEnc) currentE2EVersion + let msgBody = smpEncode $ AgentConfirmation {agentVersion = v, e2eEncryption_, encConnInfo} + msgType = agentMessageType agentMsg + msgData = SndMsgData {internalId, internalSndId, internalTs, msgType, msgBody, pqEncryption, msgFlags = SMP.MsgFlags {notification = True}, internalHash, prevMsgHash} + liftIO $ createSndMsg db connId msgData + liftIO $ createSndMsgDelivery db connId sq internalId enqueueRatchetKeyMsgs :: forall m. AgentMonad m => AgentClient -> ConnData -> NonEmpty SndQueue -> CR.RcvE2ERatchetParams 'C.X448 -> m () enqueueRatchetKeyMsgs c cData (sq :| sqs) e2eEncryption = do @@ -2546,11 +2554,11 @@ enqueueRatchetKey c cData@ConnData {connId, pqSupport} sq e2eEncryption = do pure internalId -- encoded AgentMessage -> encoded EncAgentMessage -agentRatchetEncrypt :: DB.Connection -> ConnData -> ByteString -> (VersionSMPA -> PQSupport -> Int) -> Maybe PQEncryption -> ExceptT StoreError IO (ByteString, PQEncryption) -agentRatchetEncrypt db ConnData {connId, connAgentVersion = v, pqSupport} msg getPaddedLen pqEnc_ = do +agentRatchetEncrypt :: DB.Connection -> ConnData -> ByteString -> (VersionSMPA -> PQSupport -> Int) -> Maybe PQEncryption -> CR.VersionE2E -> ExceptT StoreError IO (ByteString, PQEncryption) +agentRatchetEncrypt db ConnData {connId, connAgentVersion = v, pqSupport} msg getPaddedLen pqEnc_ currentE2EVersion = do rc <- ExceptT $ getRatchet db connId let paddedLen = getPaddedLen v pqSupport - (encMsg, rc') <- liftE (SEAgentError . cryptoError) $ CR.rcEncrypt rc paddedLen msg pqEnc_ + (encMsg, rc') <- liftE (SEAgentError . cryptoError) $ CR.rcEncrypt rc paddedLen msg pqEnc_ currentE2EVersion liftIO $ updateRatchet db connId rc' CR.SMDNoChange pure (encMsg, CR.rcSndKEM rc') diff --git a/src/Simplex/Messaging/Agent/Protocol.hs b/src/Simplex/Messaging/Agent/Protocol.hs index 660951bfc..1005b7195 100644 --- a/src/Simplex/Messaging/Agent/Protocol.hs +++ b/src/Simplex/Messaging/Agent/Protocol.hs @@ -37,9 +37,11 @@ module Simplex.Messaging.Agent.Protocol VersionSMPA, VersionRangeSMPA, pattern VersionSMPA, + duplexHandshakeSMPAgentVersion, ratchetSyncSMPAgentVersion, deliveryRcptsSMPAgentVersion, pqdrSMPAgentVersion, + currentSMPAgentVersion, supportedSMPAgentVRange, e2eEncConnInfoLength, e2eEncUserMsgLength, diff --git a/src/Simplex/Messaging/Crypto/Ratchet.hs b/src/Simplex/Messaging/Crypto/Ratchet.hs index a3251c26c..a0a1d4d10 100644 --- a/src/Simplex/Messaging/Crypto/Ratchet.hs +++ b/src/Simplex/Messaging/Crypto/Ratchet.hs @@ -76,7 +76,6 @@ module Simplex.Messaging.Crypto.Ratchet RatchetKEM (..), RatchetKEMAccepted (..), RatchetKey (..), - ratchetVersions, fullHeaderLen, applySMDiff, encodeMsgHeader, @@ -496,20 +495,17 @@ data RatchetVersions = RatchetVersions instance ToJSON RatchetVersions where -- TODO v5.7 or v5.8 change to the default record encoding - toJSON (RatchetVersions v1 v2) = toJSON (v1, v2) - toEncoding (RatchetVersions v1 v2) = toEncoding (v1, v2) + toJSON RatchetVersions {current, maxSupported} = toJSON (current, maxSupported) + toEncoding RatchetVersions {current, maxSupported} = toEncoding (current, maxSupported) instance FromJSON RatchetVersions where - -- TODO v6.0 replace with the default record parser + -- TODO v5.7 or v5.8 replace comment below with "tuple for backward" -- this parser supports JSON record encoding for forward compatibility - parseJSON v = (tupleP <|> recordP v) >>= toRV + parseJSON v = toRV <$> (tupleP <|> recordP v) where tupleP = parseJSON v recordP = J.withObject "RatchetVersions" $ \o -> (,) <$> o J..: "current" <*> o J..: "maxSupported" - toRV (v1, v2) = maybe (fail "bad version range") (pure . ratchetVersions) $ safeVersionRange v1 v2 - -ratchetVersions :: VersionRangeE2E -> RatchetVersions -ratchetVersions (VersionRange v1 v2) = RatchetVersions {current = v1, maxSupported = v2} + toRV (current, maxSupported) = RatchetVersions {current, maxSupported} data SndRatchet a = SndRatchet { rcDHRr :: PublicKey a, @@ -849,9 +845,9 @@ connPQEncryption = \case IKUsePQ -> PQSupportOn IKNoPQ pq -> pq -- default for creating connection is IKNoPQ PQEncOn -rcEncrypt :: AlgorithmI a => Ratchet a -> Int -> ByteString -> Maybe PQEncryption -> ExceptT CryptoError IO (ByteString, Ratchet a) -rcEncrypt Ratchet {rcSnd = Nothing} _ _ _ = throwE CERatchetState -rcEncrypt rc@Ratchet {rcSnd = Just sr@SndRatchet {rcCKs, rcHKs}, rcDHRs, rcKEM, rcNs, rcPN, rcAD = Str rcAD, rcSupportKEM, rcEnableKEM, rcVersion} paddedMsgLen msg pqEnc_ = do +rcEncrypt :: AlgorithmI a => Ratchet a -> Int -> ByteString -> Maybe PQEncryption -> VersionE2E -> ExceptT CryptoError IO (ByteString, Ratchet a) +rcEncrypt Ratchet {rcSnd = Nothing} _ _ _ _ = throwE CERatchetState +rcEncrypt rc@Ratchet {rcSnd = Just sr@SndRatchet {rcCKs, rcHKs}, rcDHRs, rcKEM, rcNs, rcPN, rcAD = Str rcAD, rcSupportKEM, rcEnableKEM, rcVersion} paddedMsgLen msg pqEnc_ supportedE2EVersion = do -- state.CKs, mk = KDF_CK(state.CKs) let (ck', mk, iv, ehIV) = chainKdf rcCKs v = current rcVersion @@ -862,8 +858,7 @@ rcEncrypt rc@Ratchet {rcSnd = Just sr@SndRatchet {rcCKs, rcHKs}, rcDHRs, rcKEM, -- This sets max version to support PQ encryption. -- Current version upgrade happens when peer decrypts the message. -- TODO note that maxSupported will not downgrade here below current (v). - -- TODO PQ currentE2EEncryptVersion should be passed via config - maxSupported' = max currentE2EEncryptVersion $ if pqEnc_ == Just PQEncOn then pqRatchetE2EEncryptVersion else v + maxSupported' = max supportedE2EVersion $ if pqEnc_ == Just PQEncOn then pqRatchetE2EEncryptVersion else v rcVersion' = rcVersion {maxSupported = maxSupported'} -- enc_header = HENCRYPT(state.HKs, header) (ehAuthTag, ehBody) <- encryptAEAD rcHKs ehIV (paddedHeaderLen v rcSupportKEM') rcAD (msgHeader v maxSupported') diff --git a/tests/AgentTests/DoubleRatchetTests.hs b/tests/AgentTests/DoubleRatchetTests.hs index e6057fc02..f95f07029 100644 --- a/tests/AgentTests/DoubleRatchetTests.hs +++ b/tests/AgentTests/DoubleRatchetTests.hs @@ -358,7 +358,7 @@ testVersionJSON = do testDecodeRV $ (1 :: Int, 2 :: Int) testDecodeRV $ J.object ["current" .= (1 :: Int), "maxSupported" .= (2 :: Int)] where - rv v1 v2 = ratchetVersions $ mkVersionRange (VersionE2E v1) (VersionE2E v2) + rv v1 v2 = RatchetVersions (VersionE2E v1) (VersionE2E v2) testDecodeRV :: ToJSON a => a -> Expectation testDecodeRV a = J.eitherDecode' (J.encode a) `shouldBe` Right (rv 1 2) @@ -585,7 +585,7 @@ testRatchetVersions pq = encrypt_ :: AlgorithmI a => Maybe PQEncryption -> (TVar ChaChaDRG, Ratchet a, SkippedMsgKeys) -> ByteString -> IO (Either CryptoError (ByteString, Ratchet a, SkippedMsgDiff)) encrypt_ pqEnc_ (_, rc, _) msg = -- print msg >> - runExceptT (rcEncrypt rc paddedMsgLen msg pqEnc_) + runExceptT (rcEncrypt rc paddedMsgLen msg pqEnc_ currentE2EEncryptVersion) >>= either (pure . Left) checkLength where checkLength (msg', rc') = do diff --git a/tests/AgentTests/FunctionalAPITests.hs b/tests/AgentTests/FunctionalAPITests.hs index 9c0355852..e17f44df3 100644 --- a/tests/AgentTests/FunctionalAPITests.hs +++ b/tests/AgentTests/FunctionalAPITests.hs @@ -176,7 +176,6 @@ smpCfgV7 = (smpCfg agentCfg) {serverVRange = V.mkVersionRange batchCmdsSMPVersio ntfCfgV2 :: ProtocolClientConfig NTFVersion ntfCfgV2 = (smpCfg agentCfg) {serverVRange = V.mkVersionRange (VersionNTF 1) authBatchCmdsNTFVersion} --- TODO PQ test next version with PQ agentCfgVPrev :: AgentConfig agentCfgVPrev = agentCfg @@ -187,10 +186,13 @@ agentCfgVPrev = smpCfg = smpCfgVPrev } +-- agent config for the next client version agentCfgV7 :: AgentConfig agentCfgV7 = agentCfg { sndAuthAlg = C.AuthAlg C.SX25519, + smpAgentVRange = \_ -> V.mkVersionRange duplexHandshakeSMPAgentVersion $ max pqdrSMPAgentVersion currentSMPAgentVersion, + e2eEncryptVRange = \_ -> V.mkVersionRange CR.kdfX3DHE2EEncryptVersion $ max CR.pqRatchetE2EEncryptVersion CR.currentE2EEncryptVersion, smpCfg = smpCfgV7, ntfCfg = ntfCfgV2 } @@ -436,7 +438,6 @@ canCreateQueue allowNew (srvAuth, srvVersion) (clntAuth, clntVersion) = let v = basicAuthSMPVersion in allowNew && (isNothing srvAuth || (srvVersion >= v && clntVersion >= v && srvAuth == clntAuth)) --- TODO PQ test next version with PQ testMatrix2 :: ATransport -> (PQSupport -> AgentClient -> AgentClient -> AgentMsgId -> IO ()) -> Spec testMatrix2 t runTest = do it "v7" $ withSmpServerV7 t $ runTestCfg2 agentCfgV7 agentCfgV7 3 $ runTest PQSupportOn @@ -448,9 +449,11 @@ testMatrix2 t runTest = do it "prev to current" $ withSmpServer t $ runTestCfg2 agentCfgVPrev agentCfg 3 $ runTest PQSupportOff it "current to prev" $ withSmpServer t $ runTestCfg2 agentCfg agentCfgVPrev 3 $ runTest PQSupportOff --- TODO PQ test next version with PQ testRatchetMatrix2 :: ATransport -> (PQSupport -> AgentClient -> AgentClient -> AgentMsgId -> IO ()) -> Spec testRatchetMatrix2 t runTest = do + it "ratchet next" $ withSmpServerV7 t $ runTestCfg2 agentCfgV7 agentCfgV7 3 $ runTest PQSupportOn + it "ratchet next to current" $ withSmpServerV7 t $ runTestCfg2 agentCfgV7 agentCfg 3 $ runTest PQSupportOn + it "ratchet current to next" $ withSmpServerV7 t $ runTestCfg2 agentCfg agentCfgV7 3 $ runTest PQSupportOn it "ratchet current" $ withSmpServer t $ runTestCfg2 agentCfg agentCfg 3 $ runTest PQSupportOn it "ratchet prev" $ withSmpServer t $ runTestCfg2 agentCfgRatchetVPrev agentCfgRatchetVPrev 3 $ runTest PQSupportOff it "ratchets prev to current" $ withSmpServer t $ runTestCfg2 agentCfgRatchetVPrev agentCfg 3 $ runTest PQSupportOff @@ -2478,14 +2481,12 @@ testDeliveryReceiptsVersion t = do ackMessage a' bId 10 $ Just "" get b' =##> \case ("", c, Rcvd 10) -> c == aId; _ -> False ackMessage b' aId 11 Nothing - -- TODO PQ this part hangs when waiting for Rcvd, because connection tries to upgrade to PQ encryption. - -- replacing 2 PQSupportOn with PQEncOff above prevents hanging. - -- (12, _) <- A.sendMessage a' bId PQEncOn SMP.noMsgFlags "hello 2" - -- get a' ##> ("", bId, SENT 12) - -- get b' =##> \case ("", c, Msg' 12 PQEncOff "hello 2") -> c == aId; _ -> False - -- ackMessage b' aId 12 $ Just "" - -- get a' =##> \case ("", c, Rcvd 12) -> c == bId; _ -> False - -- ackMessage a' bId 13 Nothing + (12, _) <- A.sendMessage a' bId PQEncOn SMP.noMsgFlags "hello 2" + get a' ##> ("", bId, SENT 12) + get b' =##> \case ("", c, Msg' 12 PQEncOff "hello 2") -> c == aId; _ -> False + ackMessage b' aId 12 $ Just "" + get a' =##> \case ("", c, Rcvd 12) -> c == bId; _ -> False + ackMessage a' bId 13 Nothing disconnectAgentClient a' disconnectAgentClient b'