From b4e6bc49aa020300ed10cd5f1b0cf30efb6cc84d Mon Sep 17 00:00:00 2001 From: "Evgeny @ SimpleX Chat" <259188159+evgeny-simplex@users.noreply.github.com> Date: Fri, 2 Oct 2026 21:18:35 +0000 Subject: [PATCH] refactor --- protocol/simplex-messaging.md | 5 +++-- src/Simplex/Messaging/Client.hs | 22 +++++++++------------- src/Simplex/Messaging/Protocol.hs | 29 ++++++++++++++++++++--------- src/Simplex/Messaging/Server.hs | 16 +++++++--------- 4 files changed, 39 insertions(+), 33 deletions(-) diff --git a/protocol/simplex-messaging.md b/protocol/simplex-messaging.md index 5c421b6d9..ab7550a91 100644 --- a/protocol/simplex-messaging.md +++ b/protocol/simplex-messaging.md @@ -1145,7 +1145,7 @@ Having received the `RRES` response from the destination router, proxy router wi ```abnf proxyResponse = %s"PRES" SP [responseNonce] -responseNonce = 24*24 OCTET ; from v23 +responseNonce = %s"0" / (%s"1" 24*24 OCTET) ; from v23 forwardedResponse = *OCTET ; client-encrypted SMP response, decrypted by client using per-command DH secret ``` @@ -1173,8 +1173,9 @@ The shared secret for encrypting transmission bodies between proxy router and de ```abnf relayResponse = %s"RRES" SP -responseTransmission = fwdCorrId forwardedResponse +responseTransmission = fwdCorrId [fwdResponseNonce] forwardedResponse ; fwdCorrId and forwardedResponse defined above in RFWD section +fwdResponseNonce = 24*24 OCTET ; when fwdSmpVersion is 23 or higher ``` ### Short link commands diff --git a/src/Simplex/Messaging/Client.hs b/src/Simplex/Messaging/Client.hs index 551bddfb7..313dc329d 100644 --- a/src/Simplex/Messaging/Client.hs +++ b/src/Simplex/Messaging/Client.hs @@ -142,7 +142,6 @@ import Control.Monad.Trans.Except import Crypto.Random (ChaChaDRG) import qualified Data.Aeson.TH as J import qualified Data.Attoparsec.ByteString.Char8 as A -import Data.Bifunctor (first) import Data.ByteString.Char8 (ByteString) import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Base64 as B64 @@ -163,7 +162,7 @@ import Numeric.Natural import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Encoding import Simplex.Messaging.Encoding.String -import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, sumTypeJSON) +import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, parseAll, sumTypeJSON) import Simplex.Messaging.Protocol import Simplex.Messaging.Protocol.Types import Simplex.Messaging.Server.QueueStore.QueueInfo @@ -1116,10 +1115,10 @@ deleteSMPQueues = okSMPCommands DEL -- send PRXY :: SMPServer -> Maybe BasicAuth -> Command Sender -- receives PKEY :: SessionId -> X.CertificateChain -> X.SignedExact X.PubKey -> BrokerMsg connectSMPProxiedRelay :: SMPClient -> NetworkRequestMode -> SMPServer -> Maybe BasicAuth -> ExceptT SMPClientError IO ProxiedRelay -connectSMPProxiedRelay c@ProtocolClient {client_ = PClient {tcpConnectTimeout, tcpTimeout}} nm relayServ@ProtocolServer {port = relayPort, keyHash = C.KeyHash kh} proxyAuth = +connectSMPProxiedRelay c@ProtocolClient {thParams = THandleParams {thServerVRange}, client_ = PClient {tcpConnectTimeout, tcpTimeout}} nm relayServ@ProtocolServer {port = relayPort, keyHash = C.KeyHash kh} proxyAuth = sendProtocolCommand_ c nm Nothing tOut Nothing NoEntity (Cmd SProxiedClient (PRXY relayServ proxyAuth)) >>= \case PKEY sId vr (CertChainPubKey chain key) -> - case supportedClientSMPRelayVRange `compatibleVersion` vr of + case thServerVRange `compatibleVersion` vr of Nothing -> throwE $ transportErr TEVersion Just (Compatible v) -> do relayKey <- liftEitherWith (const $ transportErr $ TEHandshake IDENTITY) =<< liftIO (runExceptT $ validateRelay chain key) @@ -1170,7 +1169,7 @@ instance StrEncoding ProxyClientError where -- consider how to process slow responses - is it handled somehow locally or delegated to the caller -- this method is used in the client -- sends PFWD :: C.PublicKeyX25519 -> EncTransmission -> Command Sender --- receives PRES :: EncResponse -> BrokerMsg -- proxy to client +-- receives PRES :: Maybe C.CbNonce -> EncResponse -> BrokerMsg -- proxy to client -- When client sends message via proxy, there may be one successful scenario and 9 error scenarios -- as shown below (WTF stands for unexpected response, ??? for response that failed to parse). @@ -1234,12 +1233,9 @@ proxySMPCommand c@ProtocolClient {thParams = proxyThParams, client_ = PClient {c let tOut = Just $ 2 * netTimeoutInt tcpTimeout nm tryE (sendProtocolCommand_ c nm (Just nonce) tOut Nothing (EntityId sessionId) (Cmd SProxiedClient (PFWD v cmdPubKey et))) >>= \case Right r -> case r of - PRES (EncResponse er) -> do + PRES nonce_ (EncResponse er) -> do -- server interaction errors are thrown directly - let (respNonce, er') - | v >= fwdNoncesSMPVersion = first C.cbNonce $ B.splitAt 24 er - | otherwise = (C.reverseNonce nonce, er) - t' <- liftEitherWith PCECryptoError $ C.cbDecrypt cmdSecret respNonce er' + t' <- liftEitherWith PCECryptoError $ C.cbDecrypt cmdSecret (fromMaybe (C.reverseNonce nonce) nonce_) er case tParse serverThParams t' of t'' :| [] -> case tDecodeClient serverThParams t'' of (_, _, cmd) -> case cmd of @@ -1260,7 +1256,7 @@ proxySMPCommand c@ProtocolClient {thParams = proxyThParams, client_ = PClient {c -- receives RRES :: EncFwdResponse -> BrokerMsg -- proxy should send PRES to the client with EncResponse -- Always uses background timeout mode -forwardSMPTransmission :: SMPClient -> CorrId -> VersionSMP -> C.PublicKeyX25519 -> EncTransmission -> ExceptT SMPClientError IO EncResponse +forwardSMPTransmission :: SMPClient -> CorrId -> VersionSMP -> C.PublicKeyX25519 -> EncTransmission -> ExceptT SMPClientError IO (Maybe C.CbNonce, EncResponse) forwardSMPTransmission c@ProtocolClient {thParams, client_ = PClient {clientCorrId = g}} fwdCorrId fwdVersion fwdKey fwdTransmission = do -- prepare params sessSecret <- case thAuth thParams of @@ -1275,8 +1271,8 @@ forwardSMPTransmission c@ProtocolClient {thParams, client_ = PClient {clientCorr RRES (EncFwdResponse efr) -> do -- unwrap r' <- liftEitherWith PCECryptoError $ C.cbDecryptNoPad sessSecret (C.reverseNonce nonce) efr - FwdResponse {fwdCorrId = _, fwdResponse} <- liftEitherWith (const $ PCEResponseError BLOCK) $ smpDecode r' - pure fwdResponse + FwdResponse {fwdCorrId = _, fwdNonce, fwdResponse} <- liftEitherWith (const $ PCEResponseError BLOCK) $ parseAll (fwdResponseP fwdVersion) r' + pure (fwdNonce, fwdResponse) r -> throwE $ unexpectedResponse r -- get queue information - always sent interactively diff --git a/src/Simplex/Messaging/Protocol.hs b/src/Simplex/Messaging/Protocol.hs index 348d15a9a..412c34584 100644 --- a/src/Simplex/Messaging/Protocol.hs +++ b/src/Simplex/Messaging/Protocol.hs @@ -170,6 +170,8 @@ module Simplex.Messaging.Protocol EncTransmission (..), encTransmissionNonce, FwdResponse (..), + encodeFwdResponse, + fwdResponseP, FwdTransmission (..), NameRecord (..), MsgFlags (..), @@ -744,7 +746,7 @@ data BrokerMsg where -- Should include certificate chain PKEY :: SessionId -> VersionRangeSMP -> CertChainPubKey -> BrokerMsg -- TLS-signed server key for proxy shared secret and initial sender key RRES :: EncFwdResponse -> BrokerMsg -- relay to proxy - PRES :: EncResponse -> BrokerMsg -- proxy to client + PRES :: Maybe C.CbNonce -> EncResponse -> BrokerMsg -- proxy to client END :: BrokerMsg ENDS :: Int64 -> IdsHash -> BrokerMsg DELD :: BrokerMsg @@ -767,15 +769,20 @@ newtype EncFwdResponse = EncFwdResponse ByteString data FwdResponse = FwdResponse { fwdCorrId :: CorrId, + fwdNonce :: Maybe C.CbNonce, fwdResponse :: EncResponse } -instance Encoding FwdResponse where - smpEncode FwdResponse {fwdCorrId = CorrId corrId, fwdResponse = EncResponse t} = - smpEncode (corrId, Tail t) - smpP = do - (corrId, Tail t) <- smpP - pure FwdResponse {fwdCorrId = CorrId corrId, fwdResponse = EncResponse t} +encodeFwdResponse :: FwdResponse -> ByteString +encodeFwdResponse FwdResponse {fwdCorrId = CorrId corrId, fwdNonce, fwdResponse = EncResponse t} = + smpEncode corrId <> maybe "" smpEncode fwdNonce <> smpEncode (Tail t) + +fwdResponseP :: VersionSMP -> Parser FwdResponse +fwdResponseP v = do + corrId <- smpP + fwdNonce <- if v >= fwdNoncesSMPVersion then Just <$> smpP else pure Nothing + Tail t <- smpP + pure FwdResponse {fwdCorrId = CorrId corrId, fwdNonce, fwdResponse = EncResponse t} newtype EncResponse = EncResponse ByteString deriving (Eq, Show) @@ -1992,7 +1999,9 @@ instance ProtocolEncoding SMPVersion ErrorType BrokerMsg where NMSG nmsgNonce encNMsgMeta -> e (NMSG_, ' ', nmsgNonce, encNMsgMeta) PKEY sid vr certKey -> e (PKEY_, ' ', sid, vr, certKey) RRES (EncFwdResponse encBlock) -> e (RRES_, ' ', Tail encBlock) - PRES (EncResponse encBlock) -> e (PRES_, ' ', Tail encBlock) + PRES nonce_ (EncResponse encBlock) + | v >= fwdNoncesSMPVersion -> e (PRES_, ' ', nonce_, Tail encBlock) + | otherwise -> e (PRES_, ' ', Tail encBlock) END -> e END_ ENDS n idsHash -> serviceResp ENDS_ n idsHash DELD -> e DELD_ @@ -2048,7 +2057,9 @@ instance ProtocolEncoding SMPVersion ErrorType BrokerMsg where NMSG_ -> NMSG <$> _smpP <*> smpP PKEY_ -> PKEY <$> _smpP <*> smpP <*> smpP RRES_ -> RRES <$> (EncFwdResponse . unTail <$> _smpP) - PRES_ -> PRES <$> (EncResponse . unTail <$> _smpP) + PRES_ + | v >= fwdNoncesSMPVersion -> PRES <$> _smpP <*> (EncResponse . unTail <$> smpP) + | otherwise -> PRES Nothing . EncResponse . unTail <$> _smpP END_ -> pure END ENDS_ -> serviceRespP ENDS DELD_ -> pure DELD diff --git a/src/Simplex/Messaging/Server.hs b/src/Simplex/Messaging/Server.hs index f39e69c0b..97dc58e60 100644 --- a/src/Simplex/Messaging/Server.hs +++ b/src/Simplex/Messaging/Server.hs @@ -1462,7 +1462,7 @@ client inc own pRequests forkProxiedCmd $ do liftIO (runExceptT (forwardSMPTransmission smp corrId fwdV pubKey encBlock) `E.catches` clientHandlers) >>= \case - Right r -> PRES r <$ inc own pSuccesses + Right (nonce_, r) -> PRES nonce_ r <$ inc own pSuccesses Left e -> ERR (smpProxyError e) <$ case e of PCEProtocolError {} -> inc own pSuccesses _ -> inc own pErrorsOther @@ -2144,15 +2144,13 @@ client TBError _ _ : _ -> throwE BLOCK TBTransmission b' _ : _ -> pure b' TBTransmissions b' _ _ : _ -> pure b' - let encrypt nonce = liftEitherWith (const BLOCK) $ C.cbEncrypt clientSecret nonce r' paddedProxiedTLength - r2 <- + fwdNonce <- if fwdVersion >= fwdNoncesSMPVersion - then do - nonce <- atomically . C.randomCbNonce =<< asks random - EncResponse . (smpEncode nonce <>) <$> encrypt nonce - else EncResponse <$> encrypt (C.reverseNonce clientNonce) - let fr = FwdResponse {fwdCorrId, fwdResponse = r2} - pure $ RRES $ EncFwdResponse $ C.cbEncryptNoPad sessSecret (C.reverseNonce proxyNonce) (smpEncode fr) + then Just <$> (atomically . C.randomCbNonce =<< asks random) + else pure Nothing + r2 <- liftEitherWith (const BLOCK) $ EncResponse <$> C.cbEncrypt clientSecret (fromMaybe (C.reverseNonce clientNonce) fwdNonce) r' paddedProxiedTLength + let fr = FwdResponse {fwdCorrId, fwdNonce, fwdResponse = r2} + pure $ RRES $ EncFwdResponse $ C.cbEncryptNoPad sessSecret (C.reverseNonce proxyNonce) (encodeFwdResponse fr) -- the inner response, or Nothing if forked (RSLV). r_ <- lift (rejectOrVerify clntThAuth t') >>= \case -- rejectOrVerify filters allowed commands, no need to repeat it here.