agent: drop versions before 07/2024 (support of PQ encryption and securing reply queue) (#1841)

* agent: drop versions before 07/2024 (support of PQ encryption and securing reply queue)

* fix encoding, add error context

* fix agent tests

* fix test

---------

Co-authored-by: sh <github.shum@liber.li>
This commit is contained in:
Evgeny
2026-08-17 12:51:45 +01:00
committed by GitHub
co-authored by sh
parent c377f2c1b1
commit d95eb47a4d
9 changed files with 279 additions and 413 deletions
+50 -73
View File
@@ -48,7 +48,7 @@ module Simplex.Messaging.Agent
createUser,
deleteUser,
setUserService,
connRequestPQSupport,
connRequestAgentVersion,
prepareConnectionToCreate,
createConnectionAsync,
setConnShortLinkAsync,
@@ -903,18 +903,16 @@ newConnNoQueues c userId enableNtfs cMode pqSupport = do
-- TODO [short links] TBC, but probably we will need async join for contact addresses as the contact will be created after user confirming the connection,
-- and join should retry, the same as 1-time invitation joins.
joinConnAsync :: AgentClient -> ACorrId -> Bool -> ConnId -> Bool -> ConnectionRequestUri c -> ConnInfo -> PQSupport -> SubscriptionMode -> AM ()
joinConnAsync c corrId updateConn connId enableNtfs cReqUri@CRInvitationUri {} cInfo pqSup subMode = do
joinConnAsync c corrId updateConn connId enableNtfs cReqUri@CRInvitationUri {} cInfo pqSupport subMode = do
when updateConn $ throwE $ CMD PROHIBITED "joinConnAsync: updateConn not allowed for invitation URI"
withInvLock c (strEncode cReqUri) "joinConnAsync" $
lift (compatibleInvitationUri cReqUri) >>= \case
Just (_, Compatible (CR.E2ERatchetParams v _ _ _), Compatible connAgentVersion) -> do
let pqSupport = pqSup `CR.pqSupportAnd` versionPQSupport_ connAgentVersion (Just v)
Just _ ->
enqueueCommand c corrId connId Nothing $ AClientCommand $ JOIN (JRConnReq enableNtfs (ACR sConnectionMode cReqUri) pqSupport) subMode cInfo
Nothing -> throwE $ AGENT A_VERSION
joinConnAsync c corrId updateConn connId enableNtfs cReqUri@CRContactUri {} cInfo pqSup subMode =
joinConnAsync c corrId updateConn connId enableNtfs cReqUri@CRContactUri {} cInfo pqSupport subMode =
lift (compatibleContactUri cReqUri) >>= \case
Just (_, rks_, Compatible connAgentVersion) -> do
let pqSupport = pqSup `CR.pqSupportAnd` versionPQSupport_ connAgentVersion (addrKeysE2EVersion <$> rks_)
Just (_, _, Compatible connAgentVersion) -> do
when updateConn $ withStore' c $ \db -> updateNewConnJoin db connId connAgentVersion pqSupport enableNtfs
enqueueCommand c corrId connId Nothing $ AClientCommand $ JOIN (JRConnReq enableNtfs (ACR sConnectionMode cReqUri) pqSupport) subMode cInfo
Nothing -> throwE $ AGENT A_VERSION
@@ -1371,21 +1369,20 @@ newQueueNtfSubscription c RcvQueue {userId, connId, server, clientNtfCreds} ntfS
liftIO $ sendNtfSubCommand ns (NSCCreate, [connId])
newConnToJoin :: forall c. AgentClient -> UserId -> ConnId -> Bool -> Maybe UTCTime -> ConnectionRequestUri c -> PQSupport -> AM ConnId
newConnToJoin c userId connId enableNtfs serviceRequestExpiresAt cReq pqSup = case cReq of
newConnToJoin c userId connId enableNtfs serviceRequestExpiresAt cReq pqSupport = case cReq of
CRInvitationUri {} ->
lift (compatibleInvitationUri cReq) >>= \case
Just (_, Compatible (CR.E2ERatchetParams v _ _ _), aVersion) -> create aVersion (Just v)
Just (_, _, aVersion) -> create aVersion
Nothing -> throwE $ AGENT A_VERSION
CRContactUri {} ->
lift (compatibleContactUri cReq) >>= \case
Just (_, rks_, aVersion) -> create aVersion (addrKeysE2EVersion <$> rks_)
Just (_, _, aVersion) -> create aVersion
Nothing -> throwE $ AGENT A_VERSION
where
create :: Compatible VersionSMPA -> Maybe CR.VersionE2E -> AM ConnId
create (Compatible connAgentVersion) e2eV_ = do
create :: Compatible VersionSMPA -> AM ConnId
create (Compatible connAgentVersion) = do
g <- asks random
let pqSupport = pqSup `CR.pqSupportAnd` versionPQSupport_ connAgentVersion e2eV_
cData = ConnData {userId, connId, connAgentVersion, enableNtfs, lastExternalSndId = 0, deleted = False, ratchetSyncState = RSOk, pqSupport, serviceRequestExpiresAt}
let cData = ConnData {userId, connId, connAgentVersion, enableNtfs, lastExternalSndId = 0, deleted = False, ratchetSyncState = RSOk, pqSupport, serviceRequestExpiresAt}
withStore c $ \db -> createNewConn db g cData SCMInvitation
newConnToAccept :: AgentClient -> UserId -> ConnId -> Bool -> InvitationId -> PQSupport -> AM ConnId
@@ -1411,12 +1408,11 @@ joinConn c nm userId connId enableNtfs cReq cInfo pqSupport subMode = do
joinConnSrv c nm userId connId enableNtfs cReq cInfo pqSupport subMode srv
startJoinInvitation :: AgentClient -> UserId -> ConnId -> Maybe SndQueue -> Bool -> ConnectionRequestUri 'CMInvitation -> PQSupport -> AM ((ConnData, SndQueue), (Maybe (CR.SndE2ERatchetParams 'C.X448), Maybe SMP.LinkId))
startJoinInvitation c userId connId sq_ enableNtfs cReqUri pqSup =
startJoinInvitation c userId connId sq_ enableNtfs cReqUri pqSupport =
lift (compatibleInvitationUri cReqUri) >>= \case
Just (qInfo, Compatible e2eRcvParams@(CR.E2ERatchetParams v _ _ _), Compatible connAgentVersion) -> do
-- this case avoids re-generating queue keys and subsequent failure of SKEY that timed out
-- e2ePubKey is always present, it's Maybe historically
let pqSupport = pqSup `CR.pqSupportAnd` versionPQSupport_ connAgentVersion (Just v)
g <- asks random
maxSupported <- asks $ maxVersion . e2eEncryptVRange . config
let cData = ConnData {userId, connId, connAgentVersion, enableNtfs, lastExternalSndId = 0, deleted = False, ratchetSyncState = RSOk, pqSupport, serviceRequestExpiresAt = Nothing}
@@ -1445,7 +1441,7 @@ startJoinInvitation c userId connId sq_ enableNtfs cReqUri pqSup =
createRatchet_ :: DB.Connection -> TVar ChaChaDRG -> ConnId -> CR.VersionE2E -> PQSupport -> CR.RcvE2ERatchetParams 'C.X448 -> ExceptT StoreError IO (CR.RatchetX448, CR.SndE2ERatchetParams 'C.X448)
createRatchet_ db g connId maxSupported pqSupport e2eRcvParams@(CR.E2ERatchetParams v _ rcDHRr kem_) = do
(pks, e2eSndParams) <- liftIO $ CR.generateSndE2EParams g v (CR.replyKEM_ v kem_ pqSupport)
(pks, e2eSndParams) <- liftIO $ CR.generateSndE2EParams g v (CR.replyKEM_ kem_ pqSupport)
(_, rcDHRs) <- atomically $ C.generateKeyPair g
rcParams <- liftEitherWith (SEAgentError . cryptoError) $ CR.pqX3dhSnd pks e2eRcvParams
let rcVs = CR.RatchetVersions {current = v, maxSupported}
@@ -1463,14 +1459,13 @@ startJoinInvitationDR c userId ConnData {connId} DRInvitation {ratchetState, rep
liftIO $ createRatchet db connId ratchetState
ExceptT $ updateNewConnSnd db connId q
connRequestPQSupport :: AgentClient -> PQSupport -> ConnectionRequestUri c -> IO (Maybe (VersionSMPA, PQSupport))
connRequestPQSupport c pqSup cReq = withAgentEnv' c $ case cReq of
CRInvitationUri {} -> invPQSupported <$$> compatibleInvitationUri cReq
where
invPQSupported (_, Compatible (CR.E2ERatchetParams e2eV _ _ _), Compatible agentV) = (agentV, pqSup `CR.pqSupportAnd` versionPQSupport_ agentV (Just e2eV))
CRContactUri {} -> ctPQSupported <$$> compatibleContactUri cReq
where
ctPQSupported (_, rks_, Compatible agentV) = (agentV, pqSup `CR.pqSupportAnd` versionPQSupport_ agentV (addrKeysE2EVersion <$> rks_))
connRequestAgentVersion :: AgentClient -> ConnectionRequestUri c -> IO (Maybe VersionSMPA)
connRequestAgentVersion c cReq = withAgentEnv' c $ case cReq of
CRInvitationUri {} -> aVersion <$$> compatibleInvitationUri cReq
CRContactUri {} -> aVersion <$$> compatibleContactUri cReq
where
aVersion :: (Compatible SMPQueueInfo, r, Compatible VersionSMPA) -> VersionSMPA
aVersion (_, _, Compatible agentV) = agentV
compatibleInvitationUri :: ConnectionRequestUri 'CMInvitation -> AM' (Maybe (Compatible SMPQueueInfo, Compatible (CR.RcvE2ERatchetParams 'C.X448), Compatible VersionSMPA))
compatibleInvitationUri (CRInvitationUri ConnReqUriData {crAgentVRange, crSmpQueues = (qUri :| _)} e2eRcvParamsUri) = do
@@ -1495,13 +1490,6 @@ compatibleContactUri (CRContactUri ConnReqUriData {crAgentVRange, crSmpQueues =
Just (ratchetKeyId, e2eRcvParams) ->
Just . (ratchetKeyId,) <$> (e2eRcvParams `compatibleVersion` e2eVR)
versionPQSupport_ :: VersionSMPA -> Maybe CR.VersionE2E -> PQSupport
versionPQSupport_ agentV e2eV_ = PQSupport $ agentV >= pqdrSMPAgentVersion && maybe True (>= CR.pqRatchetE2EEncryptVersion) e2eV_
{-# INLINE versionPQSupport_ #-}
addrKeysE2EVersion :: (RatchetKeyId, Compatible (CR.RcvE2ERatchetParams 'C.X448)) -> CR.VersionE2E
addrKeysE2EVersion (_, Compatible (CR.E2ERatchetParams e2eV _ _ _)) = e2eV
joinConnSrv :: AgentClient -> NetworkRequestMode -> UserId -> ConnId -> Bool -> ConnectionRequestUri c -> ConnInfo -> PQSupport -> SubscriptionMode -> SMPServerWithAuth -> AM SndQueueSecured
joinConnSrv c nm userId connId enableNtfs cReq cInfo pqSup subMode srv =
joinConnSrv' c nm userId connId enableNtfs cReq cInfo pqSup subMode srv $ \replyQInfo _ -> AgentConnInfoReply (replyQInfo :| []) cInfo
@@ -1522,14 +1510,14 @@ joinConnSrv' c nm userId connId enableNtfs inv@CRInvitationUri {} cInfo pqSup su
((cData, sq), (e2eSndParams, lnkId_)) <- startJoinInvitation c userId connId sq_ enableNtfs inv pqSup
secureConfirmQueue c nm cData rq_ sq srv cInfo e2eSndParams subMode
>>= (mapM_ (delInvSL c connId srv) lnkId_ $>)
joinConnSrv' c nm userId connId enableNtfs cReqUri@CRContactUri {} cInfo pqSup subMode srv mkInner =
joinConnSrv' c nm userId connId enableNtfs cReqUri@CRContactUri {} cInfo pqSupport subMode srv mkInner =
lift (compatibleContactUri cReqUri) >>= \case
Just (qInfo, ratchet_, Compatible v) ->
withInvLock c (strEncode cReqUri) "joinConnSrv" $ do
SomeConn cType conn <- withStore c (`getConn` connId)
envelope <- case ratchet_ of
Nothing -> do
let pqInitKeys = CR.joinContactInitialKeys (v >= pqdrSMPAgentVersion) pqSup
let pqInitKeys = CR.joinContactInitialKeys pqSupport
CCLink cReq _ <- case conn of
NewConnection _ -> newRcvConnSrv c NRMBackground userId connId enableNtfs SCMInvitation Nothing Nothing pqInitKeys False subMode srv
RcvConnection _ rq -> mkJoinInvitation rq pqInitKeys
@@ -1538,8 +1526,7 @@ joinConnSrv' c nm userId connId enableNtfs cReqUri@CRContactUri {} cInfo pqSup s
Just (ratchetKeyId, Compatible e2eParams@(CR.E2ERatchetParams e2eV _ _ _)) -> do
g <- asks random
e2eVR <- asks $ e2eEncryptVRange . config
let pqSupport = pqSup `CR.pqSupportAnd` versionPQSupport_ v (Just e2eV)
maxV = maxVersion e2eVR
let maxV = maxVersion e2eVR
rq <- case conn of
NewConnection _ -> do
e2eKeys <- atomically $ C.generateKeyPair g
@@ -2681,15 +2668,14 @@ ackMessage' c connId msgId rcptInfo_ = withConnLockNotify c connId "ackMessage"
del :: AM ()
del = withStore' c $ \db -> deleteMsg db connId $ InternalId msgId
sendRcpt :: Connection 'CDuplex -> AM ()
sendRcpt (DuplexConnection cData@ConnData {connAgentVersion} _ sqs) = do
sendRcpt (DuplexConnection cData _ sqs) = do
msg@RcvMsg {msgType, msgReceipt} <- withStore c $ \db -> getRcvMsg db connId $ InternalId msgId
case rcptInfo_ of
Just rcptInfo -> do
unless (msgType == AM_A_MSG_) . throwE $ CMD PROHIBITED "ackMessage: receipt not allowed"
when (connAgentVersion >= deliveryRcptsSMPAgentVersion) $ do
let RcvMsg {msgMeta = MsgMeta {sndMsgId}, internalHash} = msg
rcpt = A_RCVD [AMessageReceipt {agentMsgId = sndMsgId, msgHash = internalHash, rcptInfo}]
void $ enqueueMessages c cData sqs SMP.MsgFlags {notification = False} rcpt
let RcvMsg {msgMeta = MsgMeta {sndMsgId}, internalHash} = msg
rcpt = A_RCVD [AMessageReceipt {agentMsgId = sndMsgId, msgHash = internalHash, rcptInfo}]
void $ enqueueMessages c cData sqs SMP.MsgFlags {notification = False} rcpt
Nothing -> case (msgType, msgReceipt) of
-- only remove sent message if receipt hash was Ok, both to debug and for future redundancy
(AM_A_RCVD_, Just MsgReceipt {agentMsgId = sndMsgId, msgRcptStatus = MROk}) ->
@@ -2982,7 +2968,7 @@ connectionStats c = \case
rcvQueuesInfo = [],
sndQueuesInfo = [],
ratchetSyncState,
ratchetSyncSupported = connAgentVersion >= ratchetSyncSMPAgentVersion,
ratchetSyncSupported = True,
subStatus = Nothing
}
rcvQueueInfo :: RcvQueue -> AM RcvQueueInfo
@@ -3479,7 +3465,7 @@ processSMPTransmissions c@AgentClient {subQ} (tSess@(userId, srv, _), THandlePar
_ -> pure ()
processClientMsg srvTs msgFlags msgBody = do
clientMsg@SMP.ClientMsgEnvelope {cmHeader = SMP.PubHeader phVer e2ePubKey_} <-
parseMessage msgBody
parseMessage "4" msgBody
clientVRange <- asks $ smpClientVRange . config
unless (phVer `isCompatible` clientVRange || phVer <= agreedClientVerion) . throwE $ AGENT A_VERSION
case (e2eDhSecret, e2ePubKey_) of
@@ -3567,7 +3553,7 @@ processSMPTransmissions c@AgentClient {subQ} (tSess@(userId, srv, _), THandlePar
notify $ ERR (AGENT $ A_DUPLICATE $ Just DroppedMsg {brokerTs, attempts})
ackDel internalId
else
liftEither (parse smpP (AGENT A_MESSAGE) agentMsgBody) >>= \case
liftEither (parse smpP (AGENT $ A_MESSAGE "parse msg body 1") agentMsgBody) >>= \case
AgentMessage _ (A_MSG body) -> do
logServer "<--" c srv rId $ "MSG <MSG>:" <> logSecret' srvMsgId
notify $ MSG msgMeta msgFlags body
@@ -3602,7 +3588,7 @@ processSMPTransmissions c@AgentClient {subQ} (tSess@(userId, srv, _), THandlePar
liftIO $ lockConnForUpdate db connId
rc <- ExceptT $ getRatchetForUpdate db connId -- ratchet state pre-decryption - required for processing EREADY
(agentMsgBody, pqEncryption) <- agentRatchetDecrypt' g db connId rc encAgentMessage
liftEither (parse smpP (SEAgentError $ AGENT A_MESSAGE) agentMsgBody) >>= \case
liftEither (parse smpP (SEAgentError $ AGENT $ A_MESSAGE "parse msg body 2") agentMsgBody) >>= \case
agentMsg@(AgentMessage APrivHeader {sndMsgId, prevMsgHash} aMessage) -> do
let msgType = agentMessageType agentMsg
internalHash = C.sha256Hash agentMsgBody
@@ -3688,8 +3674,8 @@ processSMPTransmissions c@AgentClient {subQ} (tSess@(userId, srv, _), THandlePar
decryptClientMessage :: C.DhSecretX25519 -> SMP.ClientMsgEnvelope -> AM (SMP.PrivHeader, AgentMsgEnvelope)
decryptClientMessage e2eDh SMP.ClientMsgEnvelope {cmNonce, cmEncBody} = do
clientMsg <- liftEither $ agentCbDecrypt e2eDh cmNonce cmEncBody
SMP.ClientMessage privHeader clientBody <- parseMessage clientMsg
agentEnvelope <- parseMessage clientBody
SMP.ClientMessage privHeader clientBody <- parseMessage "5" clientMsg
agentEnvelope <- parseMessage "6" clientBody
-- Version check is removed here, because when connecting via v1 contact address the agent still sends v2 message,
-- to allow duplexHandshake mode, in case the receiving agent was updated to v2 after the address was created.
-- aVRange <- asks $ smpAgentVRange . config
@@ -3698,8 +3684,8 @@ processSMPTransmissions c@AgentClient {subQ} (tSess@(userId, srv, _), THandlePar
-- else throwE $ AGENT A_VERSION
pure (privHeader, agentEnvelope)
parseMessage :: Encoding a => ByteString -> AM a
parseMessage = liftEither . parse smpP (AGENT A_MESSAGE)
parseMessage :: Encoding a => String -> ByteString -> AM a
parseMessage cxt = liftEither . parse smpP (AGENT $ A_MESSAGE $ "parse message " <> cxt)
-- checking agreed versions to continue connection in case of client/agent version downgrades
checkConfVersions :: VersionSMPA -> VersionSMPC -> AM ()
@@ -3723,7 +3709,7 @@ processSMPTransmissions c@AgentClient {subQ} (tSess@(userId, srv, _), THandlePar
-- create ratchet from sent invitation and received confirmation keys
Just e2eSndParams -> do
keys <- withStore c (`getRatchetX3dhKeys` connId)
processConnInfo =<< initRcvRatchet_ agentVersion pqSupport keys e2eSndParams
processConnInfo =<< initRcvRatchet_ pqSupport keys e2eSndParams
-- use ratchet initialized from contact address ratchet keys during invitation
Nothing -> withStore' c (`getRatchet` connId) >>= \case
Left _ -> prohibited "conf: incorrect state"
@@ -3732,7 +3718,7 @@ processSMPTransmissions c@AgentClient {subQ} (tSess@(userId, srv, _), THandlePar
processConnInfo (rc, pqSupport') = do
(agentMsgBody_, rc') <- decryptConnInfo rc encConnInfo
case agentMsgBody_ of
Right agentMsgBody -> parseMessage agentMsgBody >>= \case
Right agentMsgBody -> parseMessage "1" agentMsgBody >>= \case
AgentConnInfoReply smpQueues connInfo | isNothing serviceRequestExpiresAt -> do
processConf rc' connInfo SMPConfirmation {senderKey, e2ePubKey, connInfo, smpReplyQueues = L.toList smpQueues, smpClientVersion = phVer}
withStore' c $ \db -> updateRcvMsgHash db connId 1 (InternalRcvId 0) (C.sha256Hash agentMsgBody)
@@ -3773,7 +3759,7 @@ processSMPTransmissions c@AgentClient {subQ} (tSess@(userId, srv, _), THandlePar
DuplexConnection _ (rq'@RcvQueue {smpClientVersion = v'} :| _) _ | isNothing e2eEncryption -> do
g <- asks random
(agentMsgBody, pqEncryption) <- withStore c $ \db -> runExceptT $ agentRatchetDecrypt g db connId encConnInfo
parseMessage agentMsgBody >>= \case
parseMessage "2" agentMsgBody >>= \case
AgentConnInfo connInfo -> do
notify $ INFO pqSupport connInfo
let dhSecret = C.dh' e2ePubKey e2ePrivKey
@@ -3789,15 +3775,14 @@ processSMPTransmissions c@AgentClient {subQ} (tSess@(userId, srv, _), THandlePar
_ -> prohibited "conf: incorrect state"
_ -> prohibited "conf: status /= new"
initRcvRatchet_ :: VersionSMPA -> PQSupport -> CR.RcvE2EPrivRatchetParams 'C.X448 -> CR.SndE2ERatchetParams 'C.X448 -> AM (CR.RatchetX448, PQSupport)
initRcvRatchet_ agentVersion pqSupport pks@(_, pk2, _) (CR.AE2ERatchetParams _ e2eSndParams@(CR.E2ERatchetParams e2eVersion _ _ _)) = do
initRcvRatchet_ :: PQSupport -> CR.RcvE2EPrivRatchetParams 'C.X448 -> CR.SndE2ERatchetParams 'C.X448 -> AM (CR.RatchetX448, PQSupport)
initRcvRatchet_ pqSupport pks@(_, pk2, _) (CR.AE2ERatchetParams _ e2eSndParams@(CR.E2ERatchetParams e2eVersion _ _ _)) = do
e2eEncryptVRange <- asks $ e2eEncryptVRange . config
unless (e2eVersion `isCompatible` e2eEncryptVRange) $ throwE $ AGENT A_VERSION
rcParams <- liftError cryptoError $ CR.pqX3dhRcv pks e2eSndParams
let rcVs = CR.RatchetVersions {current = e2eVersion, maxSupported = maxVersion e2eEncryptVRange}
connPQSupport = pqSupport `CR.pqSupportAnd` versionPQSupport_ agentVersion (Just e2eVersion)
rc = CR.initRcvRatchet rcVs pk2 rcParams connPQSupport
pure (rc, connPQSupport)
rc = CR.initRcvRatchet rcVs pk2 rcParams pqSupport
pure (rc, pqSupport)
decryptConnInfo :: CR.RatchetX448 -> ByteString -> AM (Either C.CryptoError ByteString, CR.RatchetX448)
decryptConnInfo rc encConnInfo = do
@@ -3957,15 +3942,10 @@ processSMPTransmissions c@AgentClient {subQ} (tSess@(userId, srv, _), THandlePar
case conn' of
ContactConnection {} -> do
-- show connection request even if invitaion via contact address is not compatible.
-- in case invitation not compatible, assume there is no PQ encryption support.
pqSupport <- lift $ maybe PQSupportOff pqSupported <$> compatibleInvitationUri connReq
invId <- storeInvitation (CRInvitation connReq) cInfo False
let srvs = L.map qServer $ crSmpQueues crData
notify $ REQ invId pqSupport srvs cInfo False
notify $ REQ invId PQSupportOn srvs cInfo False
_ -> prohibited "inv: sent to message conn"
where
pqSupported (_, Compatible (CR.E2ERatchetParams v _ _ _), Compatible agentVersion) =
PQSupportOn `CR.pqSupportAnd` versionPQSupport_ agentVersion (Just v)
storeInvitation :: ContactRequest -> ConnInfo -> Bool -> AM InvitationId
storeInvitation connReq recipientConnInfo serviceRequest = do
@@ -3983,15 +3963,15 @@ processSMPTransmissions c@AgentClient {subQ} (tSess@(userId, srv, _), THandlePar
unlessM duplicateRequest $
withStore' c (\db -> getAddressRatchetKeys db connId ratchetKeyId) >>= \case
Right (pk1, pk2, pKem) -> do
(rc, connPQSupport) <- initRcvRatchet_ agentVersion pqSupport (pk1, pk2, pKem) e2eSndParams
(rc, connPQSupport) <- initRcvRatchet_ pqSupport (pk1, pk2, pKem) e2eSndParams
(agentMsgBody_, ratchetState) <- decryptConnInfo rc encConnInfo
case agentMsgBody_ of
Right agentMsgBody -> do
let mkDR replyQueue = DRInvitation {ratchetState, replyQueue, agentVersion, pqSupport = connPQSupport}
parseMessage agentMsgBody >>= \case
parseMessage "3" agentMsgBody >>= \case
AgentConnInfoReply (replyQueue :| _) cInfo -> do
invId <- storeInvitation (CRInvitationDR $ mkDR replyQueue) cInfo False
notify $ REQ invId pqSupported (qServer replyQueue :| []) cInfo True
notify $ REQ invId PQSupportOn (qServer replyQueue :| []) cInfo True
AgentServiceRequest (replyQueue :| _) sig_ payload ->
case verifyServiceReq rc payload sig_ of
Left err -> logError ("service request: " <> T.pack err) >> notify (ERR $ AGENT $ A_SERVICE ASEBadSignature)
@@ -4003,9 +3983,6 @@ processSMPTransmissions c@AgentClient {subQ} (tSess@(userId, srv, _), THandlePar
Left _ -> prohibited "addr inv: unknown ratchetKeyId"
_ -> prohibited "inv: sent to message conn"
where
pqSupported = case e2eSndParams of
CR.AE2ERatchetParams _ (CR.E2ERatchetParams e2eVersion _ _ _) ->
PQSupportOn `CR.pqSupportAnd` versionPQSupport_ agentVersion (Just e2eVersion)
duplicateRequest = case e2eSndParams of
CR.AE2ERatchetParams _ (CR.E2ERatchetParams _ k1 k2 _) -> do
let rkHash = C.sha256Hash $ C.pubKeyBytes k1 <> C.pubKeyBytes k2
@@ -4223,16 +4200,16 @@ enqueueRatchetKey c sq@SndQueue {connId} e2eEncryption = do
pure internalId
-- encoded AgentMessage -> encoded EncAgentMessage
agentRatchetEncrypt :: DB.Connection -> ConnData -> ByteString -> (VersionSMPA -> PQSupport -> Int) -> Maybe PQEncryption -> CR.VersionE2E -> ExceptT StoreError IO (ByteString, PQEncryption)
agentRatchetEncrypt :: DB.Connection -> ConnData -> ByteString -> (PQSupport -> Int) -> Maybe PQEncryption -> CR.VersionE2E -> ExceptT StoreError IO (ByteString, PQEncryption)
agentRatchetEncrypt db cData msg getPaddedLen pqEnc_ currentE2EVersion = do
(mek, paddedLen, pqEnc) <- agentRatchetEncryptHeader db cData getPaddedLen pqEnc_ currentE2EVersion
encMsg <- withExceptT (SEAgentError . cryptoError) $ CR.rcEncryptMsg mek paddedLen msg
pure (encMsg, pqEnc)
agentRatchetEncryptHeader :: DB.Connection -> ConnData -> (VersionSMPA -> PQSupport -> Int) -> Maybe PQEncryption -> CR.VersionE2E -> ExceptT StoreError IO (CR.MsgEncryptKeyX448, Int, PQEncryption)
agentRatchetEncryptHeader db ConnData {connId, connAgentVersion = v, pqSupport} getPaddedLen pqEnc_ currentE2EVersion = do
agentRatchetEncryptHeader :: DB.Connection -> ConnData -> (PQSupport -> Int) -> Maybe PQEncryption -> CR.VersionE2E -> ExceptT StoreError IO (CR.MsgEncryptKeyX448, Int, PQEncryption)
agentRatchetEncryptHeader db ConnData {connId, pqSupport} getPaddedLen pqEnc_ currentE2EVersion = do
rc <- ExceptT $ getRatchetForUpdate db connId
let paddedLen = getPaddedLen v pqSupport
let paddedLen = getPaddedLen pqSupport
(mek, rc') <- withExceptT (SEAgentError . cryptoError) $ CR.rcEncryptHeader rc pqEnc_ currentE2EVersion
liftIO $ updateRatchet db connId rc' CR.SMDNoChange
pure (mek, paddedLen, CR.rcSndKEM rc')
+2 -2
View File
@@ -1950,7 +1950,7 @@ getQueueMessage c rq@RcvQueue {server, rcvId, rcvPrivateKey} = do
decryptSMPMessage :: RcvQueue -> SMP.RcvMessage -> AM SMP.ClientRcvMsgBody
decryptSMPMessage rq SMP.RcvMessage {msgId, msgBody = SMP.EncRcvMsgBody body} =
liftEither $ parse SMP.clientRcvMsgBodyP (AGENT A_MESSAGE) =<< decrypt body
liftEither $ parse SMP.clientRcvMsgBodyP (AGENT $ A_MESSAGE "decrypt message") =<< decrypt body
where
decrypt = agentCbDecrypt (rcvDhSecret rq) (C.cbNonce msgId)
@@ -2254,7 +2254,7 @@ agentCbDecrypt dhSecret nonce msg =
cryptoError :: C.CryptoError -> AgentErrorType
cryptoError = \case
C.CryptoLargeMsgError -> CMD LARGE "CryptoLargeMsgError"
C.CryptoHeaderError _ -> AGENT A_MESSAGE -- parsing error
C.CryptoHeaderError e -> AGENT $ A_MESSAGE $ "parse msg header " <> e
C.CERatchetDuplicateMessage -> AGENT $ A_DUPLICATE Nothing
C.AESDecryptError -> c DECRYPT_AES
C.CBDecryptError -> c DECRYPT_CB
+18 -30
View File
@@ -41,12 +41,8 @@ module Simplex.Messaging.Agent.Protocol
VersionSMPA,
VersionRangeSMPA,
pattern VersionSMPA,
duplexHandshakeSMPAgentVersion,
ratchetSyncSMPAgentVersion,
deliveryRcptsSMPAgentVersion,
pqdrSMPAgentVersion,
sndAuthKeySMPAgentVersion,
ratchetOnConfSMPAgentVersion,
rpcAddressSMPAgentVersion,
currentSMPAgentVersion,
supportedSMPAgentVRange,
e2eEncConnInfoLength,
@@ -304,6 +300,7 @@ import UnliftIO.Exception (Exception)
-- 5 - post-quantum double ratchet (3/14/2024)
-- 6 - secure reply queues with provided keys (6/14/2024)
-- 7 - initialize ratchet on processing confirmation (7/18/2024)
-- 8 - agent RPC and double ratchet PQ encryption from first message to contact address (8/01/2026)
data SMPAgentVersion
@@ -316,29 +313,20 @@ type VersionRangeSMPA = VersionRange SMPAgentVersion
pattern VersionSMPA :: Word16 -> VersionSMPA
pattern VersionSMPA v = Version v
duplexHandshakeSMPAgentVersion :: VersionSMPA
duplexHandshakeSMPAgentVersion = VersionSMPA 2
ratchetSyncSMPAgentVersion :: VersionSMPA
ratchetSyncSMPAgentVersion = VersionSMPA 3
deliveryRcptsSMPAgentVersion :: VersionSMPA
deliveryRcptsSMPAgentVersion = VersionSMPA 4
pqdrSMPAgentVersion :: VersionSMPA
pqdrSMPAgentVersion = VersionSMPA 5
sndAuthKeySMPAgentVersion :: VersionSMPA
sndAuthKeySMPAgentVersion = VersionSMPA 6
_sndAuthKeySMPAgentVersion :: VersionSMPA
_sndAuthKeySMPAgentVersion = VersionSMPA 6
ratchetOnConfSMPAgentVersion :: VersionSMPA
ratchetOnConfSMPAgentVersion = VersionSMPA 7
rpcAddressSMPAgentVersion :: VersionSMPA
rpcAddressSMPAgentVersion = VersionSMPA 8
minSupportedSMPAgentVersion :: VersionSMPA
minSupportedSMPAgentVersion = duplexHandshakeSMPAgentVersion
minSupportedSMPAgentVersion = _sndAuthKeySMPAgentVersion
currentSMPAgentVersion :: VersionSMPA
currentSMPAgentVersion = VersionSMPA 7
currentSMPAgentVersion = VersionSMPA 8
supportedSMPAgentVRange :: VersionRangeSMPA
supportedSMPAgentVRange = mkVersionRange minSupportedSMPAgentVersion currentSMPAgentVersion
@@ -346,17 +334,17 @@ supportedSMPAgentVRange = mkVersionRange minSupportedSMPAgentVersion currentSMPA
-- it is shorter to allow all handshake headers,
-- including E2E (double-ratchet) parameters and
-- signing key of the sender for the server
e2eEncConnInfoLength :: VersionSMPA -> PQSupport -> Int
e2eEncConnInfoLength v = \case
e2eEncConnInfoLength :: PQSupport -> Int
e2eEncConnInfoLength = \case
-- reduced by 3726 (roughly the increase of message ratchet header size + key and ciphertext in reply link)
PQSupportOn | v >= pqdrSMPAgentVersion -> 11106
_ -> 14832
PQSupportOn -> 11106
PQSupportOff -> 14832
e2eEncAgentMsgLength :: VersionSMPA -> PQSupport -> Int
e2eEncAgentMsgLength v = \case
e2eEncAgentMsgLength :: PQSupport -> Int
e2eEncAgentMsgLength = \case
-- reduced by 2222 (the increase of message ratchet header size)
PQSupportOn | v >= pqdrSMPAgentVersion -> 13618
_ -> 15840
PQSupportOn -> 13618
PQSupportOff -> 15840
-- | SMP agent event
type ATransmission = (ACorrId, AEntityId, AEvt)
@@ -2195,7 +2183,7 @@ data ConnectionErrorType
-- | Errors of another SMP agent.
data SMPAgentError
= -- | client or agent message that failed to parse
A_MESSAGE
A_MESSAGE {messageErr :: String}
| -- | prohibited SMP/agent message
A_PROHIBITED {prohibitedErr :: String}
| -- | incompatible version of SMP client, agent or encryption protocols
+2 -2
View File
@@ -475,8 +475,8 @@ type NoticeId = Int64
-- this function should be mirrored in the clients
ratchetSyncAllowed :: ConnData -> Bool
ratchetSyncAllowed ConnData {ratchetSyncState, connAgentVersion} =
connAgentVersion >= ratchetSyncSMPAgentVersion && (ratchetSyncState `elem` ([RSAllowed, RSRequired] :: [RatchetSyncState]))
ratchetSyncAllowed ConnData {ratchetSyncState} =
ratchetSyncState `elem` ([RSAllowed, RSRequired] :: [RatchetSyncState])
-- this function should be mirrored in the clients
ratchetSyncSendProhibited :: ConnData -> Bool
+77 -119
View File
@@ -51,8 +51,6 @@ module Simplex.Messaging.Crypto.Ratchet
VersionRangeE2E,
pattern VersionE2E,
RatchetVersions (..),
kdfX3DHE2EEncryptVersion,
pqRatchetE2EEncryptVersion,
currentE2EEncryptVersion,
supportedE2EEncryptVRange,
generateRcvE2EParams,
@@ -87,8 +85,6 @@ module Simplex.Messaging.Crypto.Ratchet
RatchetKey (..),
fullHeaderLen,
applySMDiff,
encodeMsgHeader,
msgHeaderP,
)
where
@@ -102,7 +98,6 @@ import Crypto.Random (ChaChaDRG)
import Data.Aeson (FromJSON (..), ToJSON (..))
import qualified Data.Aeson as J
import qualified Data.Aeson.TH as JQ
import Data.Attoparsec.ByteString (Parser, peekWord8')
import qualified Data.Attoparsec.ByteString.Char8 as A
import qualified Data.ByteArray as BA
import Data.ByteString.Char8 (ByteString)
@@ -132,6 +127,7 @@ import UnliftIO.STM
-- e2e encryption headers version history:
-- 1 - binary protocol encoding (1/1/2022)
-- 2 - use KDF in x3dh (10/20/2022)
-- 3 - PQDR (3/14/2024)
data E2EVersion
@@ -144,17 +140,17 @@ type VersionRangeE2E = VersionRange E2EVersion
pattern VersionE2E :: Word16 -> VersionE2E
pattern VersionE2E v = Version v
kdfX3DHE2EEncryptVersion :: VersionE2E
kdfX3DHE2EEncryptVersion = VersionE2E 2
_pqRatchetE2EEncryptVersion :: VersionE2E
_pqRatchetE2EEncryptVersion = VersionE2E 3
pqRatchetE2EEncryptVersion :: VersionE2E
pqRatchetE2EEncryptVersion = VersionE2E 3
minSupportedE2EEncryptVersion :: VersionE2E
minSupportedE2EEncryptVersion = _pqRatchetE2EEncryptVersion
currentE2EEncryptVersion :: VersionE2E
currentE2EEncryptVersion = VersionE2E 3
supportedE2EEncryptVRange :: VersionRangeE2E
supportedE2EEncryptVRange = mkVersionRange kdfX3DHE2EEncryptVersion currentE2EEncryptVersion
supportedE2EEncryptVRange = mkVersionRange minSupportedE2EEncryptVersion currentE2EEncryptVersion
data RatchetKEMState
= RKSProposed -- only KEM encapsulation key
@@ -238,9 +234,7 @@ data AnyE2ERatchetParams
deriving instance Show AnyE2ERatchetParams
instance (RatchetKEMStateI s, AlgorithmI a) => Encoding (E2ERatchetParams s a) where
smpEncode (E2ERatchetParams v k1 k2 kem_)
| v >= pqRatchetE2EEncryptVersion = smpEncode (v, k1, k2, kem_)
| otherwise = smpEncode (v, k1, k2)
smpEncode (E2ERatchetParams v k1 k2 kem_) = smpEncode (v, k1, k2, kem_)
smpP = toParams <$?> smpP
where
toParams :: AE2ERatchetParams a -> Either String (E2ERatchetParams s a)
@@ -261,14 +255,9 @@ instance Encoding AnyE2ERatchetParams where
case testEquality a a' of
Nothing -> fail "bad e2e params: different key algorithms"
Just Refl ->
kemP v >>= \case
smpP >>= \case
Just (ARKP s kem) -> pure $ AnyE2ERatchetParams s a $ E2ERatchetParams v k1 k2 (Just kem)
Nothing -> pure $ AnyE2ERatchetParams SRKSProposed a $ E2ERatchetParams v k1 k2 Nothing
where
kemP :: VersionE2E -> Parser (Maybe ARKEMParams)
kemP v
| v >= pqRatchetE2EEncryptVersion = smpP
| otherwise = pure Nothing
instance VersionI E2EVersion (E2ERatchetParams s a) where
type VersionRangeT E2EVersion (E2ERatchetParams s a) = E2ERatchetParamsUri s a
@@ -307,11 +296,10 @@ instance (RatchetKEMStateI s, AlgorithmI a) => StrEncoding (E2ERatchetParamsUri
[("v", strEncode vs), ("x3dh", strEncodeList [key1, key2])]
<> maybe [] encodeKem kem_
where
encodeKem kem
| maxVersion vs < pqRatchetE2EEncryptVersion = []
| otherwise = case kem of
RKParamsProposed k -> [("kem_key", strEncode k)]
RKParamsAccepted ct k -> [("kem_ct", strEncode ct), ("kem_key", strEncode k)]
encodeKem :: RKEMParams s -> [(ByteString, ByteString)]
encodeKem kem = case kem of
RKParamsProposed k -> [("kem_key", strEncode k)]
RKParamsAccepted ct k -> [("kem_ct", strEncode ct), ("kem_key", strEncode k)]
strP = toE2ERatchetParamsUri <$?> strP
{-# INLINE strP #-}
@@ -328,25 +316,26 @@ instance StrEncoding AnyE2ERatchetParamsUri where
strEncode (AnyE2ERatchetParamsUri _ _ ps) = strEncode ps
strP = do
query <- strP
vr :: VersionRangeE2E <- queryParam "v" query
vr :: VersionRangeE2E <- adjustE2EVRange <$> queryParam "v" query
keys <- L.toList <$> queryParam "x3dh" query
case keys of
[APublicDhKey a k1, APublicDhKey a' k2] -> case testEquality a a' of
Nothing -> fail "bad e2e params: different key algorithms"
Just Refl ->
kemP vr query >>= \case
kemP query >>= \case
Just (ARKP s kem) -> pure $ AnyE2ERatchetParamsUri s a $ E2ERatchetParamsUri vr k1 k2 (Just kem)
Nothing -> pure $ AnyE2ERatchetParamsUri SRKSProposed a $ E2ERatchetParamsUri vr k1 k2 Nothing
_ -> fail "bad e2e params"
where
kemP vr query
| maxVersion vr >= pqRatchetE2EEncryptVersion =
queryParam_ "kem_key" query
$>>= \k -> Just . kemParams k <$> queryParam_ "kem_ct" query
| otherwise = pure Nothing
kemP query =
queryParam_ "kem_key" query
$>>= \k -> Just . kemParams k <$> queryParam_ "kem_ct" query
kemParams k = \case
Nothing -> ARKP SRKSProposed $ RKParamsProposed k
Just ct -> ARKP SRKSAccepted $ RKParamsAccepted ct k
adjustE2EVRange vr =
let v = max minSupportedE2EEncryptVersion $ minVersion vr
in fromMaybe vr $ safeVersionRange v (max v $ maxVersion vr)
instance (RatchetKEMStateI s, AlgorithmI a) => Encoding (E2ERatchetParamsUri s a) where
smpEncode (E2ERatchetParamsUri vr k1 k2 kem_) = smpEncode (vr, k1, k2, kem_)
@@ -432,16 +421,14 @@ generateE2EParams g v useKEM_ = do
where
kemParams :: IO (Maybe (RKEMParams s, PrivRKEMParams s))
kemParams = case useKEM_ of
Just useKem
| v >= pqRatchetE2EEncryptVersion ->
Just <$> do
ks@(k, _) <- sntrup761Keypair g
case useKem of
ProposeKEM -> pure (RKParamsProposed k, PrivateRKParamsProposed ks)
AcceptKEM k' -> do
(ct, shared) <- sntrup761Enc g k'
pure (RKParamsAccepted ct k, PrivateRKParamsAccepted ct shared ks)
_ -> pure Nothing
Just useKem -> Just <$> do
ks@(k, _) <- sntrup761Keypair g
case useKem of
ProposeKEM -> pure (RKParamsProposed k, PrivateRKParamsProposed ks)
AcceptKEM k' -> do
(ct, shared) <- sntrup761Enc g k'
pure (RKParamsAccepted ct k, PrivateRKParamsAccepted ct shared ks)
Nothing -> pure Nothing
-- used by party initiating connection, Bob in double-ratchet spec
generateRcvE2EParams :: (AlgorithmI a, DhAlgorithm a) => TVar ChaChaDRG -> VersionE2E -> PQSupport -> IO (RcvE2EPrivRatchetParams a, RcvE2ERatchetParams a)
@@ -474,30 +461,30 @@ data RatchetInitParams = RatchetInitParams
-- this is used by the peer joining the connection
pqX3dhSnd :: DhAlgorithm a => AE2EPrivRatchetParams a -> E2ERatchetParams 'RKSProposed a -> Either CryptoError (RatchetInitParams, Maybe KEMKeyPair)
-- 3. replied 2. received
pqX3dhSnd (spk1, spk2, spKem_) (E2ERatchetParams v rk1 rk2 rKem_) = do
pqX3dhSnd (spk1, spk2, spKem_) (E2ERatchetParams _ rk1 rk2 rKem_) = do
(ks_, kem_) <- sndPq
let initParams = pqX3dh (publicKey spk1, rk1) (dh' rk1 spk2) (dh' rk2 spk1) (dh' rk2 spk2) kem_
pure (initParams, ks_)
where
sndPq :: Either CryptoError (Maybe KEMKeyPair, Maybe RatchetKEMAccepted)
sndPq = case spKem_ of
Just (APRKP _ ps) | v >= pqRatchetE2EEncryptVersion -> case (ps, rKem_) of
Just (APRKP _ ps) -> case (ps, rKem_) of
(PrivateRKParamsAccepted ct shared ks, Just (RKParamsProposed k)) -> Right (Just ks, Just $ RatchetKEMAccepted k shared ct)
(PrivateRKParamsProposed ks, _) -> Right (Just ks, Nothing) -- both parties can send "proposal" in case of ratchet renegotiation
_ -> Left CERatchetKEMState
_ -> Right (Nothing, Nothing)
Nothing -> Right (Nothing, Nothing)
-- this is used by the peer that created new connection, after receiving the reply
pqX3dhRcv :: forall s a. (RatchetKEMStateI s, DhAlgorithm a) => RcvE2EPrivRatchetParams a -> E2ERatchetParams s a -> ExceptT CryptoError IO (RatchetInitParams, Maybe KEMKeyPair)
-- 1. sent 4. received in reply
pqX3dhRcv (rpk1, rpk2, rpKem_) (E2ERatchetParams v sk1 sk2 sKem_) = do
pqX3dhRcv (rpk1, rpk2, rpKem_) (E2ERatchetParams _ sk1 sk2 sKem_) = do
kem_ <- rcvPq
let initParams = pqX3dh (sk1, publicKey rpk1) (dh' sk2 rpk1) (dh' sk1 rpk2) (dh' sk2 rpk2) (snd <$> kem_)
pure (initParams, fst <$> kem_)
where
rcvPq :: ExceptT CryptoError IO (Maybe (KEMKeyPair, RatchetKEMAccepted))
rcvPq = case sKem_ of
Just (RKParamsAccepted ct k') | v >= pqRatchetE2EEncryptVersion -> case rpKem_ of
Just (RKParamsAccepted ct k') -> case rpKem_ of
Just (PrivateRKParamsProposed ks@(_, pk)) -> do
shared <- liftIO $ sntrup761Dec ct pk
pure $ Just (ks, RatchetKEMAccepted k' shared ct)
@@ -721,31 +708,22 @@ data MsgHeader a = MsgHeader
-- to allow extension without increasing the size, the actual header length is:
-- 69 = 2 (original size) + 2 + 1+56 (Curve448) + 4 + 4
-- The exact size is 2288, added reserve
paddedHeaderLen :: VersionE2E -> PQSupport -> Int
paddedHeaderLen v = \case
PQSupportOn | v >= pqRatchetE2EEncryptVersion -> 2310
_ -> 88
paddedHeaderLen :: PQSupport -> Int
paddedHeaderLen = \case
PQSupportOn -> 2310
PQSupportOff -> 88
-- only used in tests to validate correct padding
-- (2 bytes - version size, 1 byte - header size)
fullHeaderLen :: VersionE2E -> PQSupport -> Int
fullHeaderLen v pq = 2 + 1 + paddedHeaderLen v pq + authTagSize + ivSize @AES256
fullHeaderLen :: PQSupport -> Int
fullHeaderLen pq = 2 + 1 + paddedHeaderLen pq + authTagSize + ivSize @AES256
-- pass the current version, as MsgHeader only includes the max supported version that can be different from the current
encodeMsgHeader :: AlgorithmI a => VersionE2E -> MsgHeader a -> ByteString
encodeMsgHeader v MsgHeader {msgMaxVersion, msgDHRs, msgKEM, msgPN, msgNs}
| v >= pqRatchetE2EEncryptVersion = smpEncode (msgMaxVersion, msgDHRs, msgKEM, msgPN, msgNs)
| otherwise = smpEncode (msgMaxVersion, msgDHRs, msgPN, msgNs)
-- pass the current version, as MsgHeader only includes the max supported version that can be different from the current
msgHeaderP :: AlgorithmI a => VersionE2E -> Parser (MsgHeader a)
msgHeaderP v = do
msgMaxVersion <- smpP
msgDHRs <- smpP
msgKEM <- if v >= pqRatchetE2EEncryptVersion then smpP else pure Nothing
msgPN <- smpP
msgNs <- smpP
pure MsgHeader {msgMaxVersion, msgDHRs, msgKEM, msgPN, msgNs}
instance AlgorithmI a => Encoding (MsgHeader a) where
smpEncode MsgHeader {msgMaxVersion, msgDHRs, msgKEM, msgPN, msgNs} =
smpEncode (msgMaxVersion, msgDHRs, msgKEM, msgPN, msgNs)
smpP = do
(msgMaxVersion, msgDHRs, msgKEM, msgPN, msgNs) <- smpP
pure MsgHeader {msgMaxVersion, msgDHRs, msgKEM, msgPN, msgNs}
data EncMessageHeader = EncMessageHeader
{ ehVersion :: VersionE2E, -- this is current ratchet version
@@ -757,26 +735,11 @@ data EncMessageHeader = EncMessageHeader
-- this encoding depends on version in EncMessageHeader because it is "current" ratchet version
instance Encoding EncMessageHeader where
smpEncode EncMessageHeader {ehVersion, ehIV, ehAuthTag, ehBody} =
smpEncode (ehVersion, ehIV, ehAuthTag) <> encodeLarge ehVersion ehBody
smpEncode (ehVersion, ehIV, ehAuthTag, Large ehBody)
smpP = do
(ehVersion, ehIV, ehAuthTag) <- smpP
ehBody <- largeP
(ehVersion, ehIV, ehAuthTag, Large ehBody) <- smpP
pure EncMessageHeader {ehVersion, ehIV, ehAuthTag, ehBody}
-- the encoder always uses 2-byte lengths for the new version, even for short headers without PQ keys.
encodeLarge :: VersionE2E -> ByteString -> ByteString
encodeLarge v s
| v >= pqRatchetE2EEncryptVersion = smpEncode $ Large s
| otherwise = smpEncode s
-- This parser relies on the fact that header cannot be shorter than 32 bytes (it is ~69 bytes without PQ KEM),
-- therefore if the first byte is less or equal to 31 (x1F), then we have 2 byte-length limited to 8191.
-- This allows upgrading the current version in one message.
largeP :: Parser ByteString
largeP = do
len1 <- peekWord8'
if len1 < 32 then unLarge <$> smpP else smpP
-- the header is length-prefixed to parse it as string and use as part of associated data for authenticated encryption
data EncRatchetMessage = EncRatchetMessage
{ emHeader :: ByteString,
@@ -784,15 +747,12 @@ data EncRatchetMessage = EncRatchetMessage
emBody :: ByteString
}
encodeEncRatchetMessage :: VersionE2E -> EncRatchetMessage -> ByteString
encodeEncRatchetMessage v EncRatchetMessage {emHeader, emBody, emAuthTag} =
encodeLarge v emHeader <> smpEncode (emAuthTag, Tail emBody)
encRatchetMessageP :: Parser EncRatchetMessage
encRatchetMessageP = do
emHeader <- largeP
(emAuthTag, Tail emBody) <- smpP
pure EncRatchetMessage {emHeader, emBody, emAuthTag}
instance Encoding EncRatchetMessage where
smpEncode EncRatchetMessage {emHeader, emBody, emAuthTag} =
smpEncode (Large emHeader, emAuthTag, Tail emBody)
smpP = do
(Large emHeader, emAuthTag, Tail emBody) <- smpP
pure EncRatchetMessage {emHeader, emBody, emAuthTag}
newtype PQEncryption = PQEncryption {enablePQ :: Bool}
deriving (Eq, Show)
@@ -841,15 +801,15 @@ pqEncToSupport (PQEncryption pq) = PQSupport pq
pqSupportAnd :: PQSupport -> PQSupport -> PQSupport
pqSupportAnd (PQSupport s1) (PQSupport s2) = PQSupport $ s1 && s2
pqEnableSupport :: VersionE2E -> PQSupport -> PQEncryption -> PQSupport
pqEnableSupport v (PQSupport sup) (PQEncryption enc) = PQSupport $ sup || (v >= pqRatchetE2EEncryptVersion && enc)
pqEnableSupport :: PQSupport -> PQEncryption -> PQSupport
pqEnableSupport (PQSupport sup) (PQEncryption enc) = PQSupport $ sup || enc
replyKEM_ :: VersionE2E -> Maybe (RKEMParams 'RKSProposed) -> PQSupport -> Maybe AUseKEM
replyKEM_ v kem_ = \case
PQSupportOn | v >= pqRatchetE2EEncryptVersion -> Just $ case kem_ of
replyKEM_ :: Maybe (RKEMParams 'RKSProposed) -> PQSupport -> Maybe AUseKEM
replyKEM_ kem_ = \case
PQSupportOn -> Just $ case kem_ of
Just (RKParamsProposed k) -> AUseKEM SRKSAccepted $ AcceptKEM k
Nothing -> AUseKEM SRKSProposed ProposeKEM
_ -> Nothing
PQSupportOff -> Nothing
instance StrEncoding PQEncryption where
strEncode pqMode
@@ -898,9 +858,9 @@ connPQEncryption = \case
IKUsePQ -> PQSupportOn
IKLinkPQ pq -> pq -- default for creating connection is IKLinkPQ PQEncOn
joinContactInitialKeys :: Bool -> PQSupport -> InitialKeys
joinContactInitialKeys pqCompatible = \case
PQSupportOn | pqCompatible -> IKUsePQ
joinContactInitialKeys :: PQSupport -> InitialKeys
joinContactInitialKeys = \case
PQSupportOn -> IKUsePQ
pqEnc -> IKLinkPQ pqEnc
rcCheckCanPad :: Int -> ByteString -> ExceptT CryptoError IO ()
@@ -916,14 +876,14 @@ rcEncryptHeader rc@Ratchet {rcSnd = Just sr@SndRatchet {rcCKs, rcHKs}, rcDHRs, r
-- PQ encryption can be enabled or disabled
rcEnableKEM' = fromMaybe rcEnableKEM pqEnc_
-- support for PQ encryption (and therefore large headers/small envelopes) can only be enabled, it cannot be disabled
rcSupportKEM' = pqEnableSupport v rcSupportKEM rcEnableKEM'
rcSupportKEM' = pqEnableSupport rcSupportKEM rcEnableKEM'
-- 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).
maxSupported' = max supportedE2EVersion $ if pqEnc_ == Just PQEncOn then pqRatchetE2EEncryptVersion else v
maxSupported' = max supportedE2EVersion $ if pqEnc_ == Just PQEncOn then minSupportedE2EEncryptVersion else v
rcVersion' = rcVersion {maxSupported = maxSupported'}
-- enc_header = HENCRYPT(state.HKs, header)
(ehAuthTag, ehBody) <- encryptAEAD rcHKs ehIV (paddedHeaderLen v rcSupportKEM') rcAD (msgHeader v maxSupported')
(ehAuthTag, ehBody) <- encryptAEAD rcHKs ehIV (paddedHeaderLen rcSupportKEM') rcAD (msgHeader maxSupported')
-- return enc_header
let emHeader = smpEncode EncMessageHeader {ehVersion = v, ehBody, ehAuthTag, ehIV}
msgEncryptKey =
@@ -951,9 +911,8 @@ rcEncryptHeader rc@Ratchet {rcSnd = Just sr@SndRatchet {rcCKs, rcHKs}, rcDHRs, r
-- pn = state.PN,
-- n = state.Ns
-- )
msgHeader v maxSupported' =
encodeMsgHeader
v
msgHeader maxSupported' =
smpEncode
MsgHeader
{ msgMaxVersion = maxSupported',
msgDHRs = publicKey rcDHRs,
@@ -976,11 +935,10 @@ data MsgEncryptKey a = MsgEncryptKey
deriving (Show)
rcEncryptMsg :: AlgorithmI a => MsgEncryptKey a -> Int -> ByteString -> ExceptT CryptoError IO ByteString
rcEncryptMsg MsgEncryptKey {msgKey = MessageKey mk iv, msgRcAD, msgEncHeader, msgRcVersion = v} paddedMsgLen msg = do
rcEncryptMsg MsgEncryptKey {msgKey = MessageKey mk iv, msgRcAD, msgEncHeader} paddedMsgLen msg = do
-- return ENCRYPT(mk, plaintext, CONCAT(AD, enc_header))
(emAuthTag, emBody) <- encryptAEAD mk iv paddedMsgLen (msgRcAD <> msgEncHeader) msg
let msg' = encodeEncRatchetMessage v EncRatchetMessage {emHeader = msgEncHeader, emBody, emAuthTag}
pure msg'
pure $ smpEncode EncRatchetMessage {emHeader = msgEncHeader, emBody, emAuthTag}
data SkippedMessage a
= SMMessage (DecryptResult a)
@@ -1004,7 +962,7 @@ rcDecrypt ::
ByteString ->
ExceptT CryptoError IO (DecryptResult a)
rcDecrypt g rc@Ratchet {rcRcv, rcAD = Str rcAD, rcVersion} rcMKSkipped msg' = do
encMsg@EncRatchetMessage {emHeader} <- parseE CryptoHeaderError encRatchetMessageP msg'
encMsg@EncRatchetMessage {emHeader} <- parseE CryptoHeaderError smpP msg'
encHdr <- parseE CryptoHeaderError smpP emHeader
-- plaintext = TrySkippedMessageKeysHE(state, enc_header, cipher-text, AD)
decryptSkipped encHdr encMsg >>= \case
@@ -1049,7 +1007,7 @@ rcDecrypt g rc@Ratchet {rcRcv, rcAD = Str rcAD, rcVersion} rcMKSkipped msg' = do
smkDiff :: SkippedMsgKeys -> SkippedMsgDiff
smkDiff smks = if M.null smks then SMDNoChange else SMDAdd smks
ratchetStep :: Ratchet a -> MsgHeader a -> ExceptT CryptoError IO (Ratchet a)
ratchetStep rc'@Ratchet {rcDHRs, rcRK, rcNHKs, rcNHKr, rcSupportKEM, rcVersion = rv} MsgHeader {msgDHRs, msgKEM} = do
ratchetStep rc'@Ratchet {rcDHRs, rcRK, rcNHKs, rcNHKr, rcSupportKEM} MsgHeader {msgDHRs, msgKEM} = do
(kemSS, kemSS', rcKEM') <- pqRatchetStep rc' msgKEM
-- state.DHRs = GENERATE_DH()
(_, rcDHRs') <- atomically $ generateKeyPair @a g
@@ -1064,7 +1022,7 @@ rcDecrypt g rc@Ratchet {rcRcv, rcAD = Str rcAD, rcVersion} rcMKSkipped msg' = do
rc'
{ rcDHRs = rcDHRs',
rcKEM = rcKEM',
rcSupportKEM = pqEnableSupport (current rv) rcSupportKEM rcEnableKEM',
rcSupportKEM = pqEnableSupport rcSupportKEM rcEnableKEM',
rcEnableKEM = rcEnableKEM',
rcSndKEM = PQEncryption sndKEM,
rcRcvKEM = PQEncryption rcvKEM,
@@ -1078,17 +1036,17 @@ rcDecrypt g rc@Ratchet {rcRcv, rcAD = Str rcAD, rcVersion} rcMKSkipped msg' = do
rcNHKr = rcNHKr'
}
pqRatchetStep :: Ratchet a -> Maybe ARKEMParams -> ExceptT CryptoError IO (Maybe KEMSharedKey, Maybe KEMSharedKey, Maybe RatchetKEM)
pqRatchetStep Ratchet {rcKEM, rcEnableKEM = PQEncryption pqEnc, rcVersion = rv} = \case
pqRatchetStep Ratchet {rcKEM, rcEnableKEM = PQEncryption pqEnc} = \case
-- received message does not have KEM in header,
-- but the user enabled KEM when sending previous message
Nothing -> case rcKEM of
Nothing | pqEnc && current rv >= pqRatchetE2EEncryptVersion -> do
Nothing | pqEnc -> do
rcPQRs <- liftIO $ sntrup761Keypair g
pure (Nothing, Nothing, Just RatchetKEM {rcPQRs, rcKEMs = Nothing})
_ -> pure (Nothing, Nothing, Nothing)
-- received message has KEM in header.
Just (ARKP _ ps)
| pqEnc && current rv >= pqRatchetE2EEncryptVersion -> do
| pqEnc -> do
-- state.PQRr = header.kem
(ss, rcPQRr) <- sharedSecret
-- state.PQRct = PQKEM-ENC(state.PQRr, state.PQRss) // encapsulated additional shared secret KEM #1
@@ -1156,9 +1114,9 @@ rcDecrypt g rc@Ratchet {rcRcv, rcAD = Str rcAD, rcVersion} rcMKSkipped msg' = do
e -> throwE e
-- header = HDECRYPT(state.NHKr, enc_header)
decryptNextHeader hdr = (AdvanceRatchet,) <$> decryptHeader (rcNHKr rc) hdr
decryptHeader k EncMessageHeader {ehVersion, ehBody, ehAuthTag, ehIV} = do
decryptHeader k EncMessageHeader {ehBody, ehAuthTag, ehIV} = do
header <- decryptAEAD k ehIV rcAD ehBody ehAuthTag `catchE` \_ -> throwE CERatchetHeader
parseE' CryptoHeaderError (msgHeaderP ehVersion) header
parseE' CryptoHeaderError smpP header
decryptMessage :: MessageKey -> EncRatchetMessage -> ExceptT CryptoError IO (Either CryptoError ByteString)
decryptMessage (MessageKey mk iv) EncRatchetMessage {emHeader, emBody, emAuthTag} =
-- DECRYPT(mk, cipher-text, CONCAT(AD, enc_header))
+1 -1
View File
@@ -120,6 +120,6 @@ decryptLinkData linkKey k (encFD, encMD) = do
pure (sig, s)
decode :: Encoding a => ByteString -> Either AgentErrorType a
decode = msgErr . smpDecode
msgErr = first (const $ AGENT A_MESSAGE)
msgErr = first (const $ AGENT $ A_MESSAGE "parse link data")
linkErr :: String -> Either AgentErrorType ()
linkErr = Left . AGENT . A_LINK
+28 -28
View File
@@ -26,7 +26,7 @@ import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Crypto.Ratchet
import Simplex.Messaging.Encoding
import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Protocol (EntityId (..), ProtocolServer (..), QueueMode (..), SubscriptionMode (..), currentSMPClientVersion, supportedSMPClientVRange, pattern VersionSMPC)
import Simplex.Messaging.Protocol (EntityId (..), ProtocolServer (..), QueueMode (..), currentSMPClientVersion, supportedSMPClientVRange, pattern VersionSMPC)
import Simplex.Messaging.ServiceScheme (ServiceScheme (..))
import Simplex.Messaging.Version
import Test.Hspec hiding (fit, it)
@@ -146,8 +146,8 @@ connReqData1 = connReqData {crSmpQueues = [queue1]}
connReqDataV1 :: ConnReqUriData
connReqDataV1 = connReqData {crAgentVRange = mkVersionRange (VersionSMPA 1) (VersionSMPA 1)}
connReqDataV2 :: ConnReqUriData
connReqDataV2 = connReqData {crAgentVRange = mkVersionRange (VersionSMPA 2) (VersionSMPA 2)}
connReqDataV6 :: ConnReqUriData
connReqDataV6 = connReqData {crAgentVRange = mkVersionRange (VersionSMPA 6) (VersionSMPA 6)}
connReqDataNew :: ConnReqUriData
connReqDataNew = connReqData {crSmpQueues = [queueNew]}
@@ -159,10 +159,10 @@ testDhPubKey :: C.PublicKeyX448
testDhPubKey = "MEIwBQYDK2VvAzkAmKuSYeQ/m0SixPDS8Wq8VBaTS1cW+Lp0n0h4Diu+kUpR+qXx4SDJ32YGEFoGFGSbGPry5Ychr6U="
testE2ERatchetParams :: RcvE2ERatchetParamsUri 'C.X448
testE2ERatchetParams = E2ERatchetParamsUri (mkVersionRange (VersionE2E 1) (VersionE2E 1)) testDhPubKey testDhPubKey Nothing
testE2ERatchetParams = E2ERatchetParamsUri (mkVersionRange (VersionE2E 3) (VersionE2E 3)) testDhPubKey testDhPubKey Nothing
testE2ERatchetParamsStrUri :: ByteString
testE2ERatchetParamsStrUri = "v%3D1%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D"
testE2ERatchetParamsStrUri = "v%3D3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D"
testE2ERatchetParams12 :: RcvE2ERatchetParamsUri 'C.X448
testE2ERatchetParams12 = E2ERatchetParamsUri supportedE2EEncryptVRange testDhPubKey testDhPubKey Nothing
@@ -200,8 +200,8 @@ contactConnRequest = CRContactUri connReqData Nothing
contactAddressDR :: AConnectionRequestUri
contactAddressDR = ACR SCMContact $ CRContactUri connReqData (Just (RatchetKeyId "0123456789abcdef", testE2ERatchetParams))
contactAddressV2 :: AConnectionRequestUri
contactAddressV2 = ACR SCMContact $ CRContactUri connReqDataV2 Nothing
contactAddressV6 :: AConnectionRequestUri
contactAddressV6 = ACR SCMContact $ CRContactUri connReqDataV6 Nothing
contactAddressNew :: AConnectionRequestUri
contactAddressNew = ACR SCMContact $ CRContactUri connReqDataNew Nothing
@@ -264,27 +264,27 @@ connectionRequestTests =
queueV1NoPort #== ("smp://1234-w==@smp.simplex.im/3456-w==#/?v=1-1&dh=" <> url testDhKeyStr <> "&srv=jjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion")
queueV1NoPort #== ("smp://1234-w==@smp.simplex.im,jjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion/3456-w==#" <> testDhKeyStr)
it "should serialize and parse connection invitations and contact addresses" $ do
connectionRequest #==# ("simplex:/invitation#/?v=2-7&smp=" <> url queueStr <> "&e2e=" <> testE2ERatchetParamsStrUri)
connectionRequest #== ("https://simplex.chat/invitation#/?v=2-7&smp=" <> url queueStr <> "&e2e=" <> testE2ERatchetParamsStrUri)
connectionRequestNoQM #==# ("simplex:/invitation#/?v=2-7&smp=" <> url queueStrNoQM <> "&e2e=" <> testE2ERatchetParamsStrUri)
connectionRequest1 #==# ("simplex:/invitation#/?v=2-7&smp=" <> url queue1Str <> "&e2e=" <> testE2ERatchetParamsStrUri)
connectionRequest2queues #==# ("simplex:/invitation#/?v=2-7&smp=" <> url (queueStr <> ";" <> queueStr) <> "&e2e=" <> testE2ERatchetParamsStrUri)
connectionRequestNew #==# ("simplex:/invitation#/?v=2-7&smp=" <> url queueNewStr <> "&e2e=" <> testE2ERatchetParamsStrUri)
connectionRequestNew1 #==# ("simplex:/invitation#/?v=2-7&smp=" <> url queueNew1Str <> "&e2e=" <> testE2ERatchetParamsStrUri)
connectionRequest2queuesNew #==# ("simplex:/invitation#/?v=2-7&smp=" <> url (queueNewStr <> ";" <> queueNewStr) <> "&e2e=" <> testE2ERatchetParamsStrUri)
connectionRequest #==# ("simplex:/invitation#/?v=6-8&smp=" <> url queueStr <> "&e2e=" <> testE2ERatchetParamsStrUri)
connectionRequest #== ("https://simplex.chat/invitation#/?v=6-8&smp=" <> url queueStr <> "&e2e=" <> testE2ERatchetParamsStrUri)
connectionRequestNoQM #==# ("simplex:/invitation#/?v=6-8&smp=" <> url queueStrNoQM <> "&e2e=" <> testE2ERatchetParamsStrUri)
connectionRequest1 #==# ("simplex:/invitation#/?v=6-8&smp=" <> url queue1Str <> "&e2e=" <> testE2ERatchetParamsStrUri)
connectionRequest2queues #==# ("simplex:/invitation#/?v=6-8&smp=" <> url (queueStr <> ";" <> queueStr) <> "&e2e=" <> testE2ERatchetParamsStrUri)
connectionRequestNew #==# ("simplex:/invitation#/?v=6-8&smp=" <> url queueNewStr <> "&e2e=" <> testE2ERatchetParamsStrUri)
connectionRequestNew1 #==# ("simplex:/invitation#/?v=6-8&smp=" <> url queueNew1Str <> "&e2e=" <> testE2ERatchetParamsStrUri)
connectionRequest2queuesNew #==# ("simplex:/invitation#/?v=6-8&smp=" <> url (queueNewStr <> ";" <> queueNewStr) <> "&e2e=" <> testE2ERatchetParamsStrUri)
connectionRequestV1 #== ("https://simplex.chat/invitation#/?v=1&smp=" <> url queueStr <> "&e2e=" <> testE2ERatchetParamsStrUri)
connectionRequestClientDataEmpty #==# ("simplex:/invitation#/?v=2-7&smp=" <> url queueStr <> "&e2e=" <> testE2ERatchetParamsStrUri <> "&data=" <> url "{}")
contactAddress #==# ("simplex:/contact#/?v=2-7&smp=" <> url queueStr)
contactAddressDR #==# ("simplex:/contact#/?v=2-7&smp=" <> url queueStr <> "&e2e=" <> testE2ERatchetParamsStrUri <> "&rk=MDEyMzQ1Njc4OWFiY2RlZg%3D%3D")
contactAddress #== ("https://simplex.chat/contact#/?v=2-7&smp=" <> url queueStr)
contactAddress2queues #==# ("simplex:/contact#/?v=2-7&smp=" <> url (queueStr <> ";" <> queueStr))
contactAddressNew #==# ("simplex:/contact#/?v=2-7&smp=" <> url queueNewStr)
contactAddress2queuesNew #==# ("simplex:/contact#/?v=2-7&smp=" <> url (queueNewStr <> ";" <> queueNewStr))
contactAddressV2 #==# ("simplex:/contact#/?v=2&smp=" <> url queueStr)
contactAddressV2 #== ("https://simplex.chat/contact#/?v=1&smp=" <> url queueStr) -- adjusted to v2
contactAddressV2 #== ("https://simplex.chat/contact#/?v=1-2&smp=" <> url queueStr) -- adjusted to v2
contactAddressV2 #== ("https://simplex.chat/contact#/?v=2-2&smp=" <> url queueStr)
contactAddressClientData #==# ("simplex:/contact#/?v=2-7&smp=" <> url queueStr <> "&data=" <> url "{\"type\":\"group_link\", \"group_link_id\":\"abc\"}")
connectionRequestClientDataEmpty #==# ("simplex:/invitation#/?v=6-8&smp=" <> url queueStr <> "&e2e=" <> testE2ERatchetParamsStrUri <> "&data=" <> url "{}")
contactAddress #==# ("simplex:/contact#/?v=6-8&smp=" <> url queueStr)
contactAddressDR #==# ("simplex:/contact#/?v=6-8&smp=" <> url queueStr <> "&e2e=" <> testE2ERatchetParamsStrUri <> "&rk=MDEyMzQ1Njc4OWFiY2RlZg%3D%3D")
contactAddress #== ("https://simplex.chat/contact#/?v=6-8&smp=" <> url queueStr)
contactAddress2queues #==# ("simplex:/contact#/?v=6-8&smp=" <> url (queueStr <> ";" <> queueStr))
contactAddressNew #==# ("simplex:/contact#/?v=6-8&smp=" <> url queueNewStr)
contactAddress2queuesNew #==# ("simplex:/contact#/?v=6-8&smp=" <> url (queueNewStr <> ";" <> queueNewStr))
contactAddressV6 #==# ("simplex:/contact#/?v=6&smp=" <> url queueStr)
contactAddressV6 #== ("https://simplex.chat/contact#/?v=1&smp=" <> url queueStr) -- adjusted to v6
contactAddressV6 #== ("https://simplex.chat/contact#/?v=1-2&smp=" <> url queueStr) -- adjusted to v6
contactAddressV6 #== ("https://simplex.chat/contact#/?v=2-2&smp=" <> url queueStr)
contactAddressClientData #==# ("simplex:/contact#/?v=6-8&smp=" <> url queueStr <> "&data=" <> url "{\"type\":\"group_link\", \"group_link_id\":\"abc\"}")
it "should serialize / parse queue address, connection invitations and contact addresses as binary" $ do
smpEncodingTest queue
smpEncodingTest queueNoQM -- this passes, no queue mode patch in SMPQueueUri encoding
@@ -309,7 +309,7 @@ connectionRequestTests =
smpEncodingTest (aBinaryConnReq contactAddress2queues)
smpEncodingTest (aBinaryConnReq contactAddressNew)
smpEncodingTest (aBinaryConnReq contactAddress2queuesNew)
smpEncodingTest (aBinaryConnReq contactAddressV2)
smpEncodingTest (aBinaryConnReq contactAddressV6)
smpEncodingTest (aBinaryConnReq contactAddressClientData)
it "should serialize / parse short links" $ do
CSLContact SLSServer CCTContact srv (LinkKey "0123456789abcdef0123456789abcdef") #==# "https://smp.simplex.im/a#MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY?h=jjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&p=5223&c=1234-w"
+17 -21
View File
@@ -39,8 +39,7 @@ doubleRatchetTests :: Spec
doubleRatchetTests = do
describe "double-ratchet encryption/decryption" $ do
it "should serialize and parse message header" $ do
testAlgs $ testMessageHeader kdfX3DHE2EEncryptVersion
testAlgs $ testMessageHeader $ max pqRatchetE2EEncryptVersion currentE2EEncryptVersion
testAlgs $ testMessageHeader currentE2EEncryptVersion
describe "message tests" $ runMessageTests initRatchets False
it "should encode/decode ratchet as JSON" $ do
testAlgs testKeyJSON
@@ -90,18 +89,15 @@ paddedMsgLen :: Int
paddedMsgLen = 100
fullMsgLen :: Ratchet a -> Int
fullMsgLen Ratchet {rcSupportKEM, rcVersion} = headerLenLength + fullHeaderLen v rcSupportKEM + C.authTagSize + paddedMsgLen
fullMsgLen Ratchet {rcSupportKEM} = headerLenLength + fullHeaderLen rcSupportKEM + C.authTagSize + paddedMsgLen
where
v = current rcVersion
headerLenLength
| v >= pqRatchetE2EEncryptVersion = 3 -- two bytes are added because of two Large used in new encoding
| otherwise = 1
headerLenLength = 3 -- two bytes are added because of two Large used in new encoding
testMessageHeader :: forall a. AlgorithmI a => VersionE2E -> 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 (msgHeaderP v) (encodeMsgHeader v hdr) `shouldBe` Right hdr
smpDecode (smpEncode hdr) `shouldBe` Right hdr
testKEMParams :: Expectation
testKEMParams = do
@@ -119,15 +115,15 @@ testMessageHeaderKEM _ = do
g <- C.newRandom
(k, _) <- atomically $ C.generateKeyPair @a g
(kem, _) <- sntrup761Keypair g
let msgMaxVersion = max pqRatchetE2EEncryptVersion currentE2EEncryptVersion
let msgMaxVersion = currentE2EEncryptVersion
msgKEM = Just . ARKP SRKSProposed $ RKParamsProposed kem
hdr = MsgHeader {msgMaxVersion, msgDHRs = k, msgKEM, msgPN = 0, msgNs = 0}
parseAll (msgHeaderP msgMaxVersion) (encodeMsgHeader msgMaxVersion hdr) `shouldBe` Right hdr
smpDecode (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 (msgHeaderP msgMaxVersion) (encodeMsgHeader msgMaxVersion hdr') `shouldBe` Right hdr'
smpDecode (smpEncode hdr') `shouldBe` Right hdr'
pattern Decrypted :: ByteString -> Either CryptoError (Either CryptoError ByteString)
pattern Decrypted msg <- Right (Right msg)
@@ -380,7 +376,7 @@ testEncodeDecode x = do
testX3dh :: forall a. (AlgorithmI a, DhAlgorithm a) => C.SAlgorithm a -> IO ()
testX3dh _ = do
g <- C.newRandom
let v = max pqRatchetE2EEncryptVersion currentE2EEncryptVersion
let v = currentE2EEncryptVersion
(pksBob@(_, _, Nothing), AE2ERatchetParams _ e2eBob) <- liftIO $ generateSndE2EParams @a g v Nothing
(pksAlice@(_, _, Nothing), e2eAlice) <- liftIO $ generateRcvE2EParams @a g v PQSupportOff
let paramsBob = pqX3dhSnd pksBob e2eAlice
@@ -399,7 +395,7 @@ testX3dhV1 _ = do
testPqX3dhProposeInReply :: forall a. (AlgorithmI a, DhAlgorithm a) => C.SAlgorithm a -> IO ()
testPqX3dhProposeInReply _ = do
g <- C.newRandom
let v = max pqRatchetE2EEncryptVersion currentE2EEncryptVersion
let v = currentE2EEncryptVersion
-- initiate (no KEM)
(pksAlice@(_, _, Nothing), e2eAlice) <- liftIO $ generateRcvE2EParams @a g v PQSupportOff
-- propose KEM in reply
@@ -411,7 +407,7 @@ testPqX3dhProposeInReply _ = do
testPqX3dhProposeAccept :: forall a. (AlgorithmI a, DhAlgorithm a) => C.SAlgorithm a -> IO ()
testPqX3dhProposeAccept _ = do
g <- C.newRandom
let v = max pqRatchetE2EEncryptVersion currentE2EEncryptVersion
let v = currentE2EEncryptVersion
-- initiate (propose KEM)
(pksAlice@(_, _, Just _), e2eAlice) <- liftIO $ generateRcvE2EParams @a g v PQSupportOn
E2ERatchetParams _ _ _ (Just (RKParamsProposed aliceKem)) <- pure e2eAlice
@@ -424,7 +420,7 @@ testPqX3dhProposeAccept _ = do
testPqX3dhProposeReject :: forall a. (AlgorithmI a, DhAlgorithm a) => C.SAlgorithm a -> IO ()
testPqX3dhProposeReject _ = do
g <- C.newRandom
let v = max pqRatchetE2EEncryptVersion currentE2EEncryptVersion
let v = currentE2EEncryptVersion
-- initiate (propose KEM)
(pksAlice@(_, _, Just _), e2eAlice) <- liftIO $ generateRcvE2EParams @a g v PQSupportOn
E2ERatchetParams _ _ _ (Just (RKParamsProposed _)) <- pure e2eAlice
@@ -437,7 +433,7 @@ testPqX3dhProposeReject _ = do
testPqX3dhAcceptWithoutProposalError :: forall a. (AlgorithmI a, DhAlgorithm a) => C.SAlgorithm a -> IO ()
testPqX3dhAcceptWithoutProposalError _ = do
g <- C.newRandom
let v = max pqRatchetE2EEncryptVersion currentE2EEncryptVersion
let v = currentE2EEncryptVersion
-- initiate (no KEM)
(pksAlice@(_, _, Nothing), e2eAlice) <- liftIO $ generateRcvE2EParams @a g v PQSupportOff
E2ERatchetParams _ _ _ Nothing <- pure e2eAlice
@@ -451,7 +447,7 @@ testPqX3dhAcceptWithoutProposalError _ = do
testPqX3dhProposeAgain :: forall a. (AlgorithmI a, DhAlgorithm a) => C.SAlgorithm a -> IO ()
testPqX3dhProposeAgain _ = do
g <- C.newRandom
let v = max pqRatchetE2EEncryptVersion currentE2EEncryptVersion
let v = currentE2EEncryptVersion
-- initiate (propose KEM)
(pksAlice@(_, _, Just _), e2eAlice) <- liftIO $ generateRcvE2EParams @a g v PQSupportOn
E2ERatchetParams _ _ _ (Just (RKParamsProposed _)) <- pure e2eAlice
@@ -514,7 +510,7 @@ withRatchets_ initRatchets_ test = do
initRatchets :: (AlgorithmI a, DhAlgorithm a) => IO (Ratchet a, Ratchet a, Encrypt a, Decrypt a, EncryptDecryptSpec a)
initRatchets = do
g <- C.newRandom
let v = max pqRatchetE2EEncryptVersion currentE2EEncryptVersion
let v = currentE2EEncryptVersion
(pksBob@(_, _, Nothing), AE2ERatchetParams _ e2eBob) <- liftIO $ generateSndE2EParams g v Nothing
(pksAlice@(_, pkAlice2, Nothing), e2eAlice) <- liftIO $ generateRcvE2EParams g v PQSupportOff
Right paramsBob <- pure $ pqX3dhSnd pksBob e2eAlice
@@ -528,7 +524,7 @@ initRatchets = do
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 pqRatchetE2EEncryptVersion currentE2EEncryptVersion
let v = currentE2EEncryptVersion
-- initiate (no KEM)
(pksAlice@(_, pkAlice2, Nothing), e2eAlice) <- liftIO $ generateRcvE2EParams g v PQSupportOff
-- propose KEM in reply
@@ -545,7 +541,7 @@ initRatchetsKEMProposed = do
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 pqRatchetE2EEncryptVersion currentE2EEncryptVersion
let v = currentE2EEncryptVersion
-- initiate (propose)
(pksAlice@(_, pkAlice2, Just _), e2eAlice) <- liftIO $ generateRcvE2EParams g v PQSupportOn
E2ERatchetParams _ _ _ (Just (RKParamsProposed aliceKem)) <- pure e2eAlice
@@ -563,7 +559,7 @@ initRatchetsKEMAccepted = do
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 pqRatchetE2EEncryptVersion currentE2EEncryptVersion
let v = currentE2EEncryptVersion
-- initiate (propose KEM)
(pksAlice@(_, pkAlice2, Just _), e2eAlice) <- liftIO $ generateRcvE2EParams g v PQSupportOn
-- propose KEM again in reply
+84 -137
View File
@@ -233,8 +233,9 @@ smpCfgVPrev = (smpCfg agentCfg) {serverVRange = prevRange $ serverVRange $ smpCf
-- ntfCfgVPrev :: ProtocolClientConfig NTFVersion
-- ntfCfgVPrev = (ntfCfg agentCfg) {clientALPN = Nothing, serverVRange = V.mkVersionRange (VersionNTF 1) (VersionNTF 1)}
-- currently, previous e2e version is not supported
agentCfgVPrev :: AgentConfig
agentCfgVPrev = agentCfgVPrevPQ {e2eEncryptVRange = prevRange $ e2eEncryptVRange agentCfg}
agentCfgVPrev = agentCfgVPrevPQ -- {e2eEncryptVRange = prevRange $ e2eEncryptVRange agentCfg}
agentCfgVPrevPQ :: AgentConfig
agentCfgVPrevPQ =
@@ -336,13 +337,13 @@ functionalAPITests ps = do
describe "two way concurrently (50)" $ testMatrix2Stress ps $ runAgentClientStressTestConc 50
xdescribe "two way concurrently (1000)" $ testMatrix2Stress ps $ runAgentClientStressTestConc 1000
describe "Establishing duplex connection, different PQ settings" $ do
testPQMatrix2 ps $ runAgentClientTestPQ True True
testPQMatrix2 ps $ runAgentClientTestPQ True
describe "Establishing duplex connection v2, different Ratchet versions" $
testRatchetMatrix2 ps runAgentClientTest
describe "Establish duplex connection via contact address" $
testMatrix2 ps runAgentClientContactTest
describe "Establish duplex connection via contact address, different PQ settings" $ do
testPQMatrix2NoInv ps $ runAgentClientContactTestPQ True True PQSupportOn
testPQMatrix2NoInv ps $ runAgentClientContactTestPQ True True
describe "Establish duplex connection via contact address v2, different Ratchet versions" $
testRatchetMatrix2 ps runAgentClientContactTest
describe "Establish duplex connection via contact address, different PQ settings (3 clients)" $ do
@@ -503,7 +504,7 @@ functionalAPITests ps = do
testWaitDeliveryNoPending ps
it "should delete connection after waiting for delivery to complete" $
testWaitDelivery ps
it "should delete connection if message can'ps be delivered due to AUTH error" $
it "should delete connection if message can't be delivered due to AUTH error" $
testWaitDeliveryAUTHErr ps
it "should delete connection by timeout even if message wasn't delivered" $
testWaitDeliveryTimeout ps
@@ -578,7 +579,6 @@ functionalAPITests ps = do
withSmpServer ps testRatchetAdHash
describe "Delivery receipts" $ do
it "should send and receive delivery receipt" $ withSmpServer ps testDeliveryReceipts
it "should send delivery receipt only in connection v3+" $ testDeliveryReceiptsVersion ps
it "send delivery receipts concurrently with messages" $ testDeliveryReceiptsConcurrent ps
describe "user network info" $ do
it "should wait for user network" testWaitForUserNetwork
@@ -613,27 +613,27 @@ testMatrix2 :: HasCallStack => (ASrvTransport, AStoreType) -> (PQSupport -> SndQ
testMatrix2 ps runTest = do
it "current, via proxy" $ withSmpServerProxy ps $ runTestCfgServers2 agentCfg agentCfg initAgentServersProxy 1 $ runTest PQSupportOn True True
it "current" $ withSmpServer ps $ runTestCfg2 agentCfg agentCfg 1 $ runTest PQSupportOn True False
it "prev" $ withSmpServer ps $ runTestCfg2 agentCfgVPrev agentCfgVPrev 1 $ runTest PQSupportOff False False
it "prev to current" $ withSmpServer ps $ runTestCfg2 agentCfgVPrev agentCfg 1 $ runTest PQSupportOff False False
it "current to prev" $ withSmpServer ps $ runTestCfg2 agentCfg agentCfgVPrev 1 $ runTest PQSupportOff False False
it "prev" $ withSmpServer ps $ runTestCfg2 agentCfgVPrev agentCfgVPrev 1 $ runTest PQSupportOff True False
it "prev to current" $ withSmpServer ps $ runTestCfg2 agentCfgVPrev agentCfg 1 $ runTest PQSupportOff True False
it "current to prev" $ withSmpServer ps $ runTestCfg2 agentCfg agentCfgVPrev 1 $ runTest PQSupportOff True False
testMatrix2Stress :: HasCallStack => (ASrvTransport, AStoreType) -> (PQSupport -> SndQueueSecured -> Bool -> AgentClient -> AgentClient -> AgentMsgId -> IO ()) -> Spec
testMatrix2Stress :: HasCallStack => (ASrvTransport, AStoreType) -> (PQSupport -> Bool -> AgentClient -> AgentClient -> AgentMsgId -> IO ()) -> Spec
testMatrix2Stress ps runTest = do
it "current, via proxy" $ withSmpServerProxy ps $ runTestCfgServers2 aCfg aCfg initAgentServersProxy 1 $ runTest PQSupportOn True True
it "current" $ withSmpServer ps $ runTestCfg2 aCfg aCfg 1 $ runTest PQSupportOn True False
it "prev" $ withSmpServer ps $ runTestCfg2 aCfgVPrev aCfgVPrev 1 $ runTest PQSupportOff False False
it "prev to current" $ withSmpServer ps $ runTestCfg2 aCfgVPrev aCfg 1 $ runTest PQSupportOff False False
it "current to prev" $ withSmpServer ps $ runTestCfg2 aCfg aCfgVPrev 1 $ runTest PQSupportOff False False
it "current, via proxy" $ withSmpServerProxy ps $ runTestCfgServers2 aCfg aCfg initAgentServersProxy 1 $ runTest PQSupportOn True
it "current" $ withSmpServer ps $ runTestCfg2 aCfg aCfg 1 $ runTest PQSupportOn False
it "prev" $ withSmpServer ps $ runTestCfg2 aCfgVPrev aCfgVPrev 1 $ runTest PQSupportOff False
it "prev to current" $ withSmpServer ps $ runTestCfg2 aCfgVPrev aCfg 1 $ runTest PQSupportOff False
it "current to prev" $ withSmpServer ps $ runTestCfg2 aCfg aCfgVPrev 1 $ runTest PQSupportOff False
where
aCfg = agentCfg {messageRetryInterval = fastMessageRetryInterval}
aCfgVPrev = agentCfgVPrev {messageRetryInterval = fastMessageRetryInterval}
testBasicMatrix2 :: HasCallStack => (ASrvTransport, AStoreType) -> (SndQueueSecured -> AgentClient -> AgentClient -> AgentMsgId -> IO ()) -> Spec
testBasicMatrix2 :: HasCallStack => (ASrvTransport, AStoreType) -> (AgentClient -> AgentClient -> AgentMsgId -> IO ()) -> Spec
testBasicMatrix2 ps runTest = do
it "current" $ withSmpServer ps $ runTestCfg2 agentCfg agentCfg 1 $ runTest True
it "prev" $ withSmpServer ps $ runTestCfg2 agentCfgVPrevPQ agentCfgVPrevPQ 1 $ runTest False
it "prev to current" $ withSmpServer ps $ runTestCfg2 agentCfgVPrevPQ agentCfg 1 $ runTest False
it "current to prev" $ withSmpServer ps $ runTestCfg2 agentCfg agentCfgVPrevPQ 1 $ runTest False
it "current" $ withSmpServer ps $ runTestCfg2 agentCfg agentCfg 1 runTest
it "prev" $ withSmpServer ps $ runTestCfg2 agentCfgVPrevPQ agentCfgVPrevPQ 1 runTest
it "prev to current" $ withSmpServer ps $ runTestCfg2 agentCfgVPrevPQ agentCfg 1 runTest
it "current to prev" $ withSmpServer ps $ runTestCfg2 agentCfg agentCfgVPrevPQ 1 runTest
testRatchetMatrix2 :: HasCallStack => (ASrvTransport, AStoreType) -> (PQSupport -> SndQueueSecured -> Bool -> AgentClient -> AgentClient -> AgentMsgId -> IO ()) -> Spec
testRatchetMatrix2 ps runTest = do
@@ -741,16 +741,16 @@ withAgentClients3 runTest =
runTest a b c
runAgentClientTest :: HasCallStack => PQSupport -> SndQueueSecured -> Bool -> AgentClient -> AgentClient -> AgentMsgId -> IO ()
runAgentClientTest pqSupport sqSecured viaProxy alice bob baseId =
runAgentClientTestPQ sqSecured viaProxy (alice, IKLinkPQ pqSupport) (bob, pqSupport) baseId
runAgentClientTest pqSupport _sqSecured viaProxy alice bob baseId =
runAgentClientTestPQ viaProxy (alice, IKLinkPQ pqSupport) (bob, pqSupport) baseId
runAgentClientTestPQ :: HasCallStack => SndQueueSecured -> Bool -> (AgentClient, InitialKeys) -> (AgentClient, PQSupport) -> AgentMsgId -> IO ()
runAgentClientTestPQ sqSecured viaProxy (alice, aPQ) (bob, bPQ) baseId =
runAgentClientTestPQ :: HasCallStack => Bool -> (AgentClient, InitialKeys) -> (AgentClient, PQSupport) -> AgentMsgId -> IO ()
runAgentClientTestPQ viaProxy (alice, aPQ) (bob, bPQ) baseId =
runRight_ $ do
(bobId, CCLink qInfo Nothing) <- A.createConnection alice NRMInteractive 1 True True SCMInvitation Nothing Nothing aPQ False SMSubscribe
aliceId <- A.prepareConnectionToJoin bob 1 True qInfo bPQ
sqSecured' <- A.joinConnection bob NRMInteractive 1 aliceId True qInfo "bob's connInfo" bPQ SMSubscribe
liftIO $ sqSecured' `shouldBe` sqSecured
liftIO $ sqSecured' `shouldBe` True
("", _, A.CONF confId pqSup' _ "bob's connInfo") <- get alice
liftIO $ pqSup' `shouldBe` CR.connPQEncryption aPQ
allowConnection alice bobId confId "alice's connInfo"
@@ -787,10 +787,10 @@ runAgentClientTestPQ sqSecured viaProxy (alice, aPQ) (bob, bPQ) baseId =
pqConnectionMode :: InitialKeys -> PQSupport -> Bool
pqConnectionMode pqMode1 pqMode2 = supportPQ (CR.connPQEncryption pqMode1) && supportPQ pqMode2
runAgentClientStressTestOneWay :: HasCallStack => Int64 -> PQSupport -> SndQueueSecured -> Bool -> AgentClient -> AgentClient -> AgentMsgId -> IO ()
runAgentClientStressTestOneWay n pqSupport sqSecured viaProxy alice bob baseId = runRight_ $ do
runAgentClientStressTestOneWay :: HasCallStack => Int64 -> PQSupport -> Bool -> AgentClient -> AgentClient -> AgentMsgId -> IO ()
runAgentClientStressTestOneWay n pqSupport viaProxy alice bob baseId = runRight_ $ do
let pqEnc = PQEncryption $ supportPQ pqSupport
(aliceId, bobId) <- makeConnection_ pqSupport sqSecured alice bob
(aliceId, bobId) <- makeConnection_ pqSupport alice bob
let proxySrv = if viaProxy then Just testSMPServer else Nothing
message i = "message " <> bshow i
concurrently_
@@ -819,9 +819,9 @@ runAgentClientStressTestOneWay n pqSupport sqSecured viaProxy alice bob baseId =
where
msgId = subtract baseId . fst
runAgentClientStressTestConc :: HasCallStack => Int64 -> PQSupport -> SndQueueSecured -> Bool -> AgentClient -> AgentClient -> AgentMsgId -> IO ()
runAgentClientStressTestConc n pqSupport sqSecured viaProxy alice bob _baseId = runRight_ $ do
(aliceId, bobId) <- makeConnection_ pqSupport sqSecured alice bob
runAgentClientStressTestConc :: HasCallStack => Int64 -> PQSupport -> Bool -> AgentClient -> AgentClient -> AgentMsgId -> IO ()
runAgentClientStressTestConc n pqSupport viaProxy alice bob _baseId = runRight_ $ do
(aliceId, bobId) <- makeConnection_ pqSupport alice bob
amId <- newTVarIO 0
bmId <- newTVarIO 0
let n2 = n `div` 2
@@ -883,13 +883,13 @@ testEnablePQEncryption :: HasCallStack => IO ()
testEnablePQEncryption =
withAgentClients2 $ \ca cb -> runRight_ $ do
g <- liftIO C.newRandom
(aId, bId) <- makeConnection_ PQSupportOff True ca cb
(aId, bId) <- makeConnection_ PQSupportOff ca cb
let a = (ca, aId)
b = (cb, bId)
(a, 2, "msg 1") \#>\ b
(b, 3, "msg 2") \#>\ a
-- 45 bytes is used by agent message envelope inside double ratchet message envelope
let largeMsg g' pqEnc = atomically $ C.randomBytes (e2eEncAgentMsgLength pqdrSMPAgentVersion pqEnc - 45) g'
let largeMsg g' pqEnc = atomically $ C.randomBytes (e2eEncAgentMsgLength pqEnc - 45) g'
lrg <- largeMsg g PQSupportOff
(a, 4, lrg) \#>\ b
(b, 5, lrg) \#>\ a
@@ -971,17 +971,17 @@ testAgentClient3 =
runAgentClientContactTest :: HasCallStack => PQSupport -> SndQueueSecured -> Bool -> AgentClient -> AgentClient -> AgentMsgId -> IO ()
runAgentClientContactTest pqSupport sqSecured viaProxy alice bob baseId =
runAgentClientContactTestPQ sqSecured viaProxy pqSupport (alice, IKLinkPQ pqSupport) (bob, pqSupport) baseId
runAgentClientContactTestPQ sqSecured viaProxy (alice, IKLinkPQ pqSupport) (bob, pqSupport) baseId
runAgentClientContactTestPQ :: HasCallStack => SndQueueSecured -> Bool -> PQSupport -> (AgentClient, InitialKeys) -> (AgentClient, PQSupport) -> AgentMsgId -> IO ()
runAgentClientContactTestPQ sqSecured viaProxy reqPQSupport (alice, aPQ) (bob, bPQ) baseId =
runAgentClientContactTestPQ :: HasCallStack => SndQueueSecured -> Bool -> (AgentClient, InitialKeys) -> (AgentClient, PQSupport) -> AgentMsgId -> IO ()
runAgentClientContactTestPQ sqSecured viaProxy (alice, aPQ) (bob, bPQ) baseId =
runRight_ $ do
(_, CCLink qInfo Nothing) <- A.createConnection alice NRMInteractive 1 True True SCMContact Nothing Nothing aPQ False SMSubscribe
aliceId <- A.prepareConnectionToJoin bob 1 True qInfo bPQ
sqSecuredJoin <- A.joinConnection bob NRMInteractive 1 aliceId True qInfo "bob's connInfo" bPQ SMSubscribe
liftIO $ sqSecuredJoin `shouldBe` False -- joining via contact address connection
("", _, A.REQ invId pqSup' _ "bob's connInfo" _) <- get alice
liftIO $ pqSup' `shouldBe` reqPQSupport
liftIO $ pqSup' `shouldBe` PQSupportOn
bobId <- A.prepareConnectionToAccept alice 1 True invId (CR.connPQEncryption aPQ)
sqSecured' <- acceptContact alice 1 bobId True invId "alice's connInfo" (CR.connPQEncryption aPQ) SMSubscribe
liftIO $ sqSecured' `shouldBe` sqSecured
@@ -2056,86 +2056,86 @@ connReqWithKeys cr rk = case cr of
testIncreaseConnAgentVersion :: HasCallStack => (ASrvTransport, AStoreType) -> IO ()
testIncreaseConnAgentVersion ps = do
alice <- getSMPAgentClient' 1 agentCfg {smpAgentVRange = mkVersionRange 1 2} initAgentServers testDB
bob <- getSMPAgentClient' 2 agentCfg {smpAgentVRange = mkVersionRange 1 2} initAgentServers testDB2
alice <- getSMPAgentClient' 1 agentCfg {smpAgentVRange = mkVersionRange 6 7} initAgentServers testDB
bob <- getSMPAgentClient' 2 agentCfg {smpAgentVRange = mkVersionRange 6 7} initAgentServers testDB2
withSmpServerStoreMsgLogOn ps testPort $ \_ -> do
(aliceId, bobId) <- runRight $ do
(aliceId, bobId) <- makeConnection_ PQSupportOff False alice bob
(aliceId, bobId) <- makeConnection_ PQSupportOff alice bob
exchangeGreetingsMsgId_ PQEncOff 2 alice bobId bob aliceId
checkVersion alice bobId 2
checkVersion bob aliceId 2
checkVersion alice bobId 7
checkVersion bob aliceId 7
pure (aliceId, bobId)
-- version doesn't increase if incompatible
disposeAgentClient alice
threadDelay 250000
alice2 <- getSMPAgentClient' 3 agentCfg {smpAgentVRange = mkVersionRange 1 3} initAgentServers testDB
alice2 <- getSMPAgentClient' 3 agentCfg {smpAgentVRange = mkVersionRange 6 8} initAgentServers testDB
runRight_ $ do
subscribeConnection alice2 bobId
exchangeGreetingsMsgId_ PQEncOff 4 alice2 bobId bob aliceId
checkVersion alice2 bobId 2
checkVersion bob aliceId 2
checkVersion alice2 bobId 7
checkVersion bob aliceId 7
-- version increases if compatible
disposeAgentClient bob
threadDelay 250000
bob2 <- getSMPAgentClient' 4 agentCfg {smpAgentVRange = mkVersionRange 1 3} initAgentServers testDB2
bob2 <- getSMPAgentClient' 4 agentCfg {smpAgentVRange = mkVersionRange 6 8} initAgentServers testDB2
runRight_ $ do
subscribeConnection bob2 aliceId
exchangeGreetingsMsgId_ PQEncOff 6 alice2 bobId bob2 aliceId
checkVersion alice2 bobId 3
checkVersion bob2 aliceId 3
checkVersion alice2 bobId 8
checkVersion bob2 aliceId 8
-- version doesn't decrease, even if incompatible
disposeAgentClient alice2
threadDelay 250000
alice3 <- getSMPAgentClient' 5 agentCfg {smpAgentVRange = mkVersionRange 2 2} initAgentServers testDB
alice3 <- getSMPAgentClient' 5 agentCfg {smpAgentVRange = mkVersionRange 7 7} initAgentServers testDB
runRight_ $ do
subscribeConnection alice3 bobId
exchangeGreetingsMsgId_ PQEncOff 8 alice3 bobId bob2 aliceId
checkVersion alice3 bobId 3
checkVersion bob2 aliceId 3
checkVersion alice3 bobId 8
checkVersion bob2 aliceId 8
disposeAgentClient bob2
threadDelay 250000
bob3 <- getSMPAgentClient' 6 agentCfg {smpAgentVRange = mkVersionRange 1 1} initAgentServers testDB2
bob3 <- getSMPAgentClient' 6 agentCfg {smpAgentVRange = mkVersionRange 6 6} initAgentServers testDB2
runRight_ $ do
subscribeConnection bob3 aliceId
exchangeGreetingsMsgId_ PQEncOff 10 alice3 bobId bob3 aliceId
checkVersion alice3 bobId 3
checkVersion bob3 aliceId 3
checkVersion alice3 bobId 8
checkVersion bob3 aliceId 8
disposeAgentClient alice3
disposeAgentClient bob3
checkVersion :: AgentClient -> ConnId -> Word16 -> ExceptT AgentErrorType IO ()
checkVersion :: HasCallStack => AgentClient -> ConnId -> Word16 -> ExceptT AgentErrorType IO ()
checkVersion c connId v = do
ConnectionStats {connAgentVersion} <- getConnectionServers c connId
liftIO $ connAgentVersion `shouldBe` VersionSMPA v
testIncreaseConnAgentVersionMaxCompatible :: HasCallStack => (ASrvTransport, AStoreType) -> IO ()
testIncreaseConnAgentVersionMaxCompatible ps = do
alice <- getSMPAgentClient' 1 agentCfg {smpAgentVRange = mkVersionRange 1 2} initAgentServers testDB
bob <- getSMPAgentClient' 2 agentCfg {smpAgentVRange = mkVersionRange 1 2} initAgentServers testDB2
alice <- getSMPAgentClient' 1 agentCfg {smpAgentVRange = mkVersionRange 6 7} initAgentServers testDB
bob <- getSMPAgentClient' 2 agentCfg {smpAgentVRange = mkVersionRange 6 7} initAgentServers testDB2
withSmpServerStoreMsgLogOn ps testPort $ \_ -> do
(aliceId, bobId) <- runRight $ do
(aliceId, bobId) <- makeConnection_ PQSupportOff False alice bob
(aliceId, bobId) <- makeConnection_ PQSupportOff alice bob
exchangeGreetingsMsgId_ PQEncOff 2 alice bobId bob aliceId
checkVersion alice bobId 2
checkVersion bob aliceId 2
checkVersion alice bobId 7
checkVersion bob aliceId 7
pure (aliceId, bobId)
-- version increases to max compatible
disposeAgentClient alice
threadDelay 250000
alice2 <- getSMPAgentClient' 3 agentCfg {smpAgentVRange = mkVersionRange 1 3} initAgentServers testDB
alice2 <- getSMPAgentClient' 3 agentCfg {smpAgentVRange = mkVersionRange 6 8} initAgentServers testDB
disposeAgentClient bob
threadDelay 250000
bob2 <- getSMPAgentClient' 4 agentCfg {smpAgentVRange = supportedSMPAgentVRange} initAgentServers testDB2
@@ -2144,34 +2144,34 @@ testIncreaseConnAgentVersionMaxCompatible ps = do
subscribeConnection alice2 bobId
subscribeConnection bob2 aliceId
exchangeGreetingsMsgId_ PQEncOff 4 alice2 bobId bob2 aliceId
checkVersion alice2 bobId 3
checkVersion bob2 aliceId 3
checkVersion alice2 bobId 8
checkVersion bob2 aliceId 8
disposeAgentClient alice2
disposeAgentClient bob2
testIncreaseConnAgentVersionStartDifferentVersion :: HasCallStack => (ASrvTransport, AStoreType) -> IO ()
testIncreaseConnAgentVersionStartDifferentVersion ps = do
alice <- getSMPAgentClient' 1 agentCfg {smpAgentVRange = mkVersionRange 1 2} initAgentServers testDB
bob <- getSMPAgentClient' 2 agentCfg {smpAgentVRange = mkVersionRange 1 3} initAgentServers testDB2
alice <- getSMPAgentClient' 1 agentCfg {smpAgentVRange = mkVersionRange 6 7} initAgentServers testDB
bob <- getSMPAgentClient' 2 agentCfg {smpAgentVRange = mkVersionRange 6 8} initAgentServers testDB2
withSmpServerStoreMsgLogOn ps testPort $ \_ -> do
(aliceId, bobId) <- runRight $ do
(aliceId, bobId) <- makeConnection_ PQSupportOff False alice bob
(aliceId, bobId) <- makeConnection_ PQSupportOff alice bob
exchangeGreetingsMsgId_ PQEncOff 2 alice bobId bob aliceId
checkVersion alice bobId 2
checkVersion bob aliceId 2
checkVersion alice bobId 7
checkVersion bob aliceId 7
pure (aliceId, bobId)
-- version increases to max compatible
disposeAgentClient alice
threadDelay 250000
alice2 <- getSMPAgentClient' 3 agentCfg {smpAgentVRange = mkVersionRange 1 3} initAgentServers testDB
alice2 <- getSMPAgentClient' 3 agentCfg {smpAgentVRange = mkVersionRange 6 8} initAgentServers testDB
runRight_ $ do
subscribeConnection alice2 bobId
exchangeGreetingsMsgId_ PQEncOff 4 alice2 bobId bob aliceId
checkVersion alice2 bobId 3
checkVersion bob aliceId 3
checkVersion alice2 bobId 8
checkVersion bob aliceId 8
disposeAgentClient alice2
disposeAgentClient bob
@@ -2731,20 +2731,20 @@ testOnlyCreatePull = withAgentClients2 $ \alice bob -> runRight_ $ do
getMSGNTF alice bobId
makeConnection :: AgentClient -> AgentClient -> ExceptT AgentErrorType IO (ConnId, ConnId)
makeConnection = makeConnection_ PQSupportOn True
makeConnection = makeConnection_ PQSupportOn
makeConnection_ :: PQSupport -> SndQueueSecured -> AgentClient -> AgentClient -> ExceptT AgentErrorType IO (ConnId, ConnId)
makeConnection_ pqEnc sqSecured alice bob = makeConnectionForUsers_ pqEnc sqSecured alice 1 bob 1
makeConnection_ :: PQSupport -> AgentClient -> AgentClient -> ExceptT AgentErrorType IO (ConnId, ConnId)
makeConnection_ pqEnc alice bob = makeConnectionForUsers_ pqEnc alice 1 bob 1
makeConnectionForUsers :: HasCallStack => AgentClient -> UserId -> AgentClient -> UserId -> ExceptT AgentErrorType IO (ConnId, ConnId)
makeConnectionForUsers = makeConnectionForUsers_ PQSupportOn True
makeConnectionForUsers = makeConnectionForUsers_ PQSupportOn
makeConnectionForUsers_ :: HasCallStack => PQSupport -> SndQueueSecured -> AgentClient -> UserId -> AgentClient -> UserId -> ExceptT AgentErrorType IO (ConnId, ConnId)
makeConnectionForUsers_ pqSupport sqSecured alice aliceUserId bob bobUserId = do
makeConnectionForUsers_ :: HasCallStack => PQSupport -> AgentClient -> UserId -> AgentClient -> UserId -> ExceptT AgentErrorType IO (ConnId, ConnId)
makeConnectionForUsers_ pqSupport alice aliceUserId bob bobUserId = do
(bobId, CCLink qInfo Nothing) <- A.createConnection alice NRMInteractive aliceUserId True True SCMInvitation Nothing Nothing (IKLinkPQ pqSupport) False SMSubscribe
aliceId <- A.prepareConnectionToJoin bob bobUserId True qInfo pqSupport
sqSecured' <- A.joinConnection bob NRMInteractive bobUserId aliceId True qInfo "bob's connInfo" pqSupport SMSubscribe
liftIO $ sqSecured' `shouldBe` sqSecured
liftIO $ sqSecured' `shouldBe` True
("", _, A.CONF confId pqSup' _ "bob's connInfo") <- get alice
liftIO $ pqSup' `shouldBe` pqSupport
allowConnection alice bobId confId "alice's connInfo"
@@ -2872,7 +2872,7 @@ testBatchedSubscriptions :: Int -> Int -> (ASrvTransport, AStoreType) -> IO ()
testBatchedSubscriptions nCreate nDel ps@(t, ASType qsType _) = do
(conns, conns') <- withAgentClientsCfgServers2 agentCfg agentCfg initAgentServers2 $ \a b -> do
conns <- runServers $ do
conns <- replicateM nCreate $ makeConnection_ PQSupportOff True a b
conns <- replicateM nCreate $ makeConnection_ PQSupportOff a b
forM_ conns $ \(aId, bId) -> exchangeGreetings_ PQEncOff a bId b aId
let (aIds', bIds') = unzip $ take nDel conns
delete a bIds'
@@ -3017,8 +3017,8 @@ receiveMsg c cId msgId msg = do
get c =##> \case ("", cId', Msg' mId' PQEncOn msg') -> cId' == cId && mId' == msgId && msg' == msg; _ -> False
ackMessage c cId msgId Nothing
testAsyncCommands :: SndQueueSecured -> AgentClient -> AgentClient -> AgentMsgId -> IO ()
testAsyncCommands sqSecured alice bob baseId =
testAsyncCommands :: AgentClient -> AgentClient -> AgentMsgId -> IO ()
testAsyncCommands alice bob baseId =
runRight_ $ do
bobId <- prepareConnectionToCreate alice 1 True SCMInvitation PQSupportOn
createConnectionAsync alice "1" bobId True SCMInvitation IKPQOn False SMSubscribe
@@ -3029,7 +3029,7 @@ testAsyncCommands sqSecured alice bob baseId =
("2", aliceId', JOINED sqSecured') <- get bob
liftIO $ do
aliceId' `shouldBe` aliceId
sqSecured' `shouldBe` sqSecured
sqSecured' `shouldBe` True
("", _, CONF confId _ "bob's connInfo") <- get alice
allowConnectionAsync alice "3" bobId confId "alice's connInfo"
get alice =##> \case ("3", _, OK) -> True; _ -> False
@@ -3145,8 +3145,8 @@ testAsyncCommandsRestore ps = do
get alice' =##> \case ("1", _, INV _) -> True; _ -> False
pure ()
testAcceptContactAsync :: SndQueueSecured -> AgentClient -> AgentClient -> AgentMsgId -> IO ()
testAcceptContactAsync sqSecured alice bob baseId =
testAcceptContactAsync :: AgentClient -> AgentClient -> AgentMsgId -> IO ()
testAcceptContactAsync alice bob baseId =
runRight_ $ do
(_, qInfo) <- createConnection alice 1 True SCMContact Nothing SMSubscribe
(aliceId, sqSecuredJoin) <- joinConnection bob 1 True qInfo "bob's connInfo" SMSubscribe
@@ -3154,7 +3154,7 @@ testAcceptContactAsync sqSecured alice bob baseId =
("", _, REQ invId _ "bob's connInfo") <- get alice
bobId <- prepareConnectionToAccept alice 1 True invId PQSupportOn
acceptContactAsync alice "1" bobId True invId "alice's connInfo" PQSupportOn SMSubscribe
get alice =##> \case ("1", c, JOINED sqSecured') -> c == bobId && sqSecured' == sqSecured; _ -> False
get alice =##> \case ("1", c, JOINED sqSecured') -> c == bobId && sqSecured' == True; _ -> False
("", _, CONF confId _ "alice's connInfo") <- get bob
allowConnection bob aliceId confId "bob's connInfo"
get alice ##> ("", bobId, INFO "bob's connInfo")
@@ -3978,59 +3978,6 @@ testDeliveryReceipts =
ackMessage b aId 5 (Just "") `catchError` \case (A.CMD PROHIBITED _) -> pure (); e -> liftIO $ expectationFailure ("unexpected error " <> show e)
ackMessage b aId 5 Nothing
testDeliveryReceiptsVersion :: HasCallStack => (ASrvTransport, AStoreType) -> IO ()
testDeliveryReceiptsVersion ps = do
a <- getSMPAgentClient' 1 agentCfg {smpAgentVRange = mkVersionRange 1 3} initAgentServers testDB
b <- getSMPAgentClient' 2 agentCfg {smpAgentVRange = mkVersionRange 1 3} initAgentServers testDB2
withSmpServerStoreMsgLogOn ps testPort $ \_ -> do
(aId, bId) <- runRight $ do
(aId, bId) <- makeConnection_ PQSupportOff False a b
checkVersion a bId 3
checkVersion b aId 3
(2, _) <- A.sendMessage a bId PQEncOff SMP.noMsgFlags "hello"
get a ##> ("", bId, SENT 2)
get b =##> \case ("", c, Msg' 2 PQEncOff "hello") -> c == aId; _ -> False
ackMessage b aId 2 $ Just ""
liftIO $ noMessages a "no delivery receipt (unsupported version)"
(3, _) <- A.sendMessage b aId PQEncOff SMP.noMsgFlags "hello too"
get b ##> ("", aId, SENT 3)
get a =##> \case ("", c, Msg' 3 PQEncOff "hello too") -> c == bId; _ -> False
ackMessage a bId 3 $ Just ""
liftIO $ noMessages b "no delivery receipt (unsupported version)"
pure (aId, bId)
disposeAgentClient a
disposeAgentClient b
a' <- getSMPAgentClient' 3 agentCfg {smpAgentVRange = supportedSMPAgentVRange} initAgentServers testDB
b' <- getSMPAgentClient' 4 agentCfg {smpAgentVRange = supportedSMPAgentVRange} initAgentServers testDB2
runRight_ $ do
subscribeConnection a' bId
subscribeConnection b' aId
exchangeGreetingsMsgId_ PQEncOff 4 a' bId b' aId
checkVersion a' bId 7
checkVersion b' aId 7
(6, PQEncOff) <- A.sendMessage a' bId PQEncOn SMP.noMsgFlags "hello"
get a' ##> ("", bId, SENT 6)
get b' =##> \case ("", c, Msg' 6 PQEncOff "hello") -> c == aId; _ -> False
ackMessage b' aId 6 $ Just ""
get a' =##> \case ("", c, Rcvd 6) -> c == bId; _ -> False
ackMessage a' bId 7 Nothing
(8, PQEncOff) <- A.sendMessage b' aId PQEncOn SMP.noMsgFlags "hello too"
get b' ##> ("", aId, SENT 8)
get a' =##> \case ("", c, Msg' 8 PQEncOff "hello too") -> c == bId; _ -> False
ackMessage a' bId 8 $ Just ""
get b' =##> \case ("", c, Rcvd 8) -> c == aId; _ -> False
ackMessage b' aId 9 Nothing
(10, _) <- A.sendMessage a' bId PQEncOn SMP.noMsgFlags "hello 2"
get a' ##> ("", bId, SENT 10)
get b' =##> \case ("", c, Msg' 10 PQEncOff "hello 2") -> c == aId; _ -> False
ackMessage b' aId 10 $ Just ""
get a' =##> \case ("", c, Rcvd 10) -> c == bId; _ -> False
ackMessage a' bId 11 Nothing
disposeAgentClient a'
disposeAgentClient b'
testDeliveryReceiptsConcurrent :: HasCallStack => (ASrvTransport, AStoreType) -> IO ()
testDeliveryReceiptsConcurrent (t, msType) =
withSmpServerConfigOn t cfg' testPort $ \_ -> do