mirror of
https://github.com/simplex-chat/simplexmq.git
synced 2026-08-28 22:18:18 +00:00
double ratchet versioning for post-quantum encryption (#1025)
* correctly parse new Ratchet fields when omitted * rfc: migrating connection versions to pqdr * update rfc * WIP (dont commit) * rename versions * update ratchet version based on PQ encryption feature flag * remove duplicate function * synchronize ratchet, fix tests, refactor * comments * test * pattern
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
# Migrating existing connections to post-quantum double ratchet algorithm
|
||||
|
||||
## Problem
|
||||
|
||||
Post-quantum variant of double ratchet algorithm represents an almost full-stack change affecting all parts of the protocol stack except client-server protocol (SMP):
|
||||
- double-ratchet end-to-end encryption: different encoding (additional large keys require byte-strings larger than 255 bytes with 2-byte length prefixes) and larger message headers (increased by ~2200 bytes).
|
||||
- agent-agent protocol: a smaller maximum message size to accomodate larger headers and to fit in 16kb blocks, reduced by ~2200 bytes for the messages and by almost ~4000 bytes for connection information.
|
||||
- chat protocol: also a smaller message size compensated by zstd comression of JSON messages.
|
||||
|
||||
We want the versioning that achieves these objectives:
|
||||
- all changes in all protocol layers happen at the same time, when both clients support it.
|
||||
- ability to downgrade the clients to the previous version without losing connection.
|
||||
- ability to opt-in into this functionality via "experimental" feature toggle, that enables post-quantum encryption in connections when both contacts enable this toggle.
|
||||
|
||||
To have ability to downgrade the clients we have two options:
|
||||
- roll-out this functionality in two stages: 1) roll-out clients support but do not enable the new version, and then 2) upgrade client version. The problem here is that the clients won't be able to opt-in into this experiment.
|
||||
- make offered range dependent on experimental feature being enabled. Currently we have an option to enable PQ encryption in agent API, and this option can be used as a proxy to maxium supported protocol version - if the option is passed, it can be seen as an indication that higher version range (or version) should offered (or accepted).
|
||||
|
||||
## Solution
|
||||
|
||||
Currently ratchet state stores version range. It's unclear what was the intended semantics of that version range - it simply stores the offered/supported version range at the time ratchet was initialised, but only a high bound is used to send in message headers, and it is never upgraded. In JSON this range is encoded as tuple (an array of two elements in JSON).
|
||||
|
||||
We could continue using this range with the meaning of the lower bound to be "currently used ratchet version" and the meaning of higher boundary to be "maximum supported ratchet version". We could also use the version communicated in message headers to upgrade ratchet version, with the condition that upgrade should only happen if both sides want it. Currently it's defined by pqEnableKEM property in ratchet state. We could also make it more explicit by defining maximum version to which ratchet should upgrade. Given that irreversible upgrades are not very common, it is probably ok to keep it implicit.
|
||||
|
||||
We can define a better type than VersionRange to reflect semantics of the range in ratchet (current/max supported range), but for backward compatibility it needs to be encoded in the same way as now.
|
||||
|
||||
To summarize, the proposed solution for ratchet versioning is:
|
||||
- define ratchet versions as new type to include current and maximum allowed versions, where maximum allowed will be either the same or lower than maximum supported based on PQ option (in 5.6), and in 5.7 it will be changed to maximum supported, so version starts upgrading independently from PQ being enabled.
|
||||
- make encodings in ratchet depend on current version (in curent code it depends on max version).
|
||||
- include max allowed in message header.
|
||||
- upgrade current if in range on each new message if less than max and higher than current (same as we do for connections).
|
||||
- increase max allowed once PQ is enabled (only in 5.6). Make max allowed the same as max supported (global constant).
|
||||
|
||||
```haskell
|
||||
data RatchetVR = RatchetVR
|
||||
{ currentVersion :: Version,
|
||||
maxAllowedVersion :: Version
|
||||
}
|
||||
|
||||
instance ToJSON RatchetVR where
|
||||
toEncoding (RatchetVR v1 v2) = toEncoding (v1, v2)
|
||||
toJSON (RatchetVR v1 v2) = toJSON (v1, v2)
|
||||
|
||||
instance FromJSON RatchetVR where
|
||||
parseJSON v = do
|
||||
-- this also verifies that v2 > v1 (although we could remove JSON instances for VersionRange)
|
||||
VersionRange v1 v2 <- parseJSON v
|
||||
pure $ RatchetVR v1 v2
|
||||
```
|
||||
|
||||
For connections, we could also make version used for the purposes of encoding dependent on the PQ being enabled, and version for decoding taken from message header, but then we'd have to not only upgrade ratchets but the connection as well every time PQ mode changes.
|
||||
|
||||
Another suggestion to ensure that correct version range is used in correct contexts could be:
|
||||
- using different newtypes for different version ranges.
|
||||
- define generic type class for version aware encoding that would also accept only specific type class for the version to use the correct range. This may be justified as there will be several version-aware encodings, and not just the protocol as now.
|
||||
|
||||
```haskell
|
||||
class Ord v => EncodingV v a where
|
||||
{-# MINIMAL smpEncodeV, (smpDecodeV | smpVP) #-}
|
||||
smpEncodeV :: v -> a -> ByteString
|
||||
-- default decode uses parser
|
||||
smpDecodeV :: v -> ByteString -> Either String a
|
||||
smpDecodeV = parseAll . smpVP
|
||||
-- default parser decodes from length-specified bytestring
|
||||
smpVP :: v -> Parser a
|
||||
smpVP v = smpDecodeV v <$?> smpP
|
||||
```
|
||||
|
||||
The version will be passed from currently agreed version, it may only change when message is received, not when message is sent. The version will not be extracted from the encoding itself as it happens now in ratchet encodings.
|
||||
|
||||
## Various options how the problem can be simplified
|
||||
|
||||
1. Do not support connection downgrade once both devices upgraded. If applied to all existing connections then it is a bad option, as it would disrupt some important conversations.
|
||||
|
||||
2. Do not provide ability to opt-in into PQ encryption until v5.7 where it will be rolled out automatically. That is also suboptimal, as it won't allow announcing technology design and have testing outside of the team devices.
|
||||
|
||||
3. The logic explained above where connection upgrade and downgrade is possible and applied to all existing connections if both parties consent to it. There are these important downsides:
|
||||
- complexity of this logic
|
||||
- regression risks when this logic is removed.
|
||||
- some non-coordinated upgrades of existing, potentially important conversations, simply because two users opt-in into the experiment without any expectation that another side also opts-in.
|
||||
|
||||
4. Apply upgrade/downgrade logic and enable PQ encryption as opt-in, based on the toggle in the UX, only for the new connections. This seems the least risky, and also simpler than option 3, as it would only apply to the new connections, and both users will have to enable experimental toggle prior to connecting.
|
||||
|
||||
Option 4 seems the best trade-off, and has these sub-options regarding where it is controlled:
|
||||
a) in chat based on connection flag. Chat will pass PQ options only to connections that were created when experimental option was enabled.
|
||||
b) in agent - there will be additional logic to ignore PQ option for existing connections.
|
||||
c) both in chat and in agent.
|
||||
|
||||
Option 4a seems better, as it would:
|
||||
- simplify agent code
|
||||
- minimise required changes when releasing v5.7 (as we do want that all direct and small groups connections migrate to PQ encryption at the time, without any toggles)
|
||||
- allow tests for connection upgrade in the currect code.
|
||||
@@ -558,14 +558,14 @@ newConnAsync c userId corrId enableNtfs cMode pqInitKeys subMode = do
|
||||
newConnNoQueues :: AgentMonad m => AgentClient -> UserId -> ConnId -> Bool -> SConnectionMode c -> CR.PQEncryption -> m ConnId
|
||||
newConnNoQueues c userId connId enableNtfs cMode pqEncryption = do
|
||||
g <- asks random
|
||||
connAgentVersion <- asks $ maxVersion . smpAgentVRange . config
|
||||
connAgentVersion <- asks $ maxVersion . ($ pqEncryption) . smpAgentVRange . config
|
||||
let cData = ConnData {userId, connId, connAgentVersion, enableNtfs, lastExternalSndId = 0, deleted = False, ratchetSyncState = RSOk, pqEncryption}
|
||||
withStore c $ \db -> createNewConn db g cData cMode
|
||||
|
||||
joinConnAsync :: AgentMonad m => AgentClient -> UserId -> ACorrId -> Bool -> ConnectionRequestUri c -> ConnInfo -> CR.PQEncryption -> SubscriptionMode -> m ConnId
|
||||
joinConnAsync c userId corrId enableNtfs cReqUri@(CRInvitationUri ConnReqUriData {crAgentVRange} _) cInfo pqEncryption subMode = do
|
||||
withInvLock c (strEncode cReqUri) "joinConnAsync" $ do
|
||||
aVRange <- asks $ smpAgentVRange . config
|
||||
aVRange <- asks $ ($ pqEncryption) . smpAgentVRange . config
|
||||
case crAgentVRange `compatibleVersion` aVRange of
|
||||
Just (Compatible connAgentVersion) -> do
|
||||
g <- asks random
|
||||
@@ -667,14 +667,16 @@ newRcvConnSrv c userId connId enableNtfs cMode clientData pqInitKeys subMode srv
|
||||
when enableNtfs $ do
|
||||
ns <- asks ntfSupervisor
|
||||
atomically $ sendNtfSubCommand ns (connId, NSCCreate)
|
||||
let crData = ConnReqUriData SSSimplex smpAgentVRange [qUri] clientData
|
||||
let pqEnc = CR.connPQEncryption pqInitKeys
|
||||
crData = ConnReqUriData SSSimplex (smpAgentVRange pqEnc) [qUri] clientData
|
||||
e2eVRange = e2eEncryptVRange pqEnc
|
||||
case cMode of
|
||||
SCMContact -> pure (connId, CRContactUri crData)
|
||||
SCMInvitation -> do
|
||||
g <- asks random
|
||||
(pk1, pk2, pKem, e2eRcvParams) <- liftIO $ CR.generateRcvE2EParams g (maxVersion e2eEncryptVRange) (CR.initialPQEncryption pqInitKeys)
|
||||
(pk1, pk2, pKem, e2eRcvParams) <- liftIO $ CR.generateRcvE2EParams g (maxVersion e2eVRange) (CR.initialPQEncryption pqInitKeys)
|
||||
withStore' c $ \db -> createRatchetX3dhKeys db connId pk1 pk2 pKem
|
||||
pure (connId, CRInvitationUri crData $ toVersionRangeT e2eRcvParams e2eEncryptVRange)
|
||||
pure (connId, CRInvitationUri crData $ toVersionRangeT e2eRcvParams e2eVRange)
|
||||
|
||||
joinConn :: AgentMonad m => AgentClient -> UserId -> ConnId -> Bool -> ConnectionRequestUri c -> ConnInfo -> CR.PQEncryption -> SubscriptionMode -> m ConnId
|
||||
joinConn c userId connId enableNtfs cReq cInfo pqEnc subMode = do
|
||||
@@ -687,17 +689,18 @@ joinConn c userId connId enableNtfs cReq cInfo pqEnc subMode = do
|
||||
startJoinInvitation :: AgentMonad m => UserId -> ConnId -> Bool -> ConnectionRequestUri 'CMInvitation -> CR.PQEncryption -> m (Compatible VersionSMPA, ConnData, NewSndQueue, CR.Ratchet 'C.X448, CR.SndE2ERatchetParams 'C.X448)
|
||||
startJoinInvitation userId connId enableNtfs (CRInvitationUri ConnReqUriData {crAgentVRange, crSmpQueues = (qUri :| _)} e2eRcvParamsUri) pqEncryption = do
|
||||
AgentConfig {smpClientVRange, smpAgentVRange, e2eEncryptVRange} <- asks config
|
||||
let e2eVRange = e2eEncryptVRange pqEncryption
|
||||
case ( qUri `compatibleVersion` smpClientVRange,
|
||||
e2eRcvParamsUri `compatibleVersion` e2eEncryptVRange,
|
||||
crAgentVRange `compatibleVersion` smpAgentVRange
|
||||
e2eRcvParamsUri `compatibleVersion` e2eVRange,
|
||||
crAgentVRange `compatibleVersion` smpAgentVRange pqEncryption
|
||||
) of
|
||||
(Just qInfo, Just (Compatible e2eRcvParams@(CR.E2ERatchetParams v _ rcDHRr kem_)), Just aVersion@(Compatible connAgentVersion)) -> do
|
||||
g <- asks random
|
||||
(pk1, pk2, pKem, e2eSndParams) <- liftIO $ CR.generateSndE2EParams g v (CR.replyKEM_ pqEncryption kem_)
|
||||
(_, rcDHRs) <- atomically $ C.generateKeyPair g
|
||||
-- TODO PQ generate KEM keypair if needed - is it done?
|
||||
rcParams <- liftEitherWith cryptoError $ CR.pqX3dhSnd pk1 pk2 pKem e2eRcvParams
|
||||
let rc = CR.initSndRatchet e2eEncryptVRange rcDHRr rcDHRs rcParams
|
||||
let rcVs = CR.RVersions {current = v, maxSupported = maxVersion e2eVRange}
|
||||
rc = CR.initSndRatchet rcVs rcDHRr rcDHRs rcParams
|
||||
q <- newSndQueue userId "" qInfo
|
||||
let cData = ConnData {userId, connId, connAgentVersion, enableNtfs, lastExternalSndId = 0, deleted = False, ratchetSyncState = RSOk, pqEncryption}
|
||||
pure (aVersion, cData, q, rc, e2eSndParams)
|
||||
@@ -720,7 +723,7 @@ joinConnSrv c userId connId enableNtfs inv@CRInvitationUri {} cInfo pqEnc subMod
|
||||
void $ withStore' c $ \db -> deleteConn db Nothing connId'
|
||||
throwError e
|
||||
joinConnSrv c userId connId enableNtfs (CRContactUri ConnReqUriData {crAgentVRange, crSmpQueues = (qUri :| _)}) cInfo pqEnc subMode srv = do
|
||||
aVRange <- asks $ smpAgentVRange . config
|
||||
aVRange <- asks $ ($ pqEnc) . smpAgentVRange . config
|
||||
clientVRange <- asks $ smpClientVRange . config
|
||||
case ( qUri `compatibleVersion` clientVRange,
|
||||
crAgentVRange `compatibleVersion` aVRange
|
||||
@@ -1107,23 +1110,24 @@ enqueueMessage c cData sq pqEnc_ msgFlags aMessage =
|
||||
-- this function is used only for sending messages in batch, it returns the list of successes to enqueue additional deliveries
|
||||
enqueueMessageB :: forall m t. (AgentMonad' m, Traversable t) => AgentClient -> t (Either AgentErrorType (ConnData, NonEmpty SndQueue, Maybe CR.PQEncryption, MsgFlags, AMessage)) -> m (t (Either AgentErrorType ((AgentMsgId, CR.PQEncryption), Maybe (ConnData, [SndQueue], AgentMsgId))))
|
||||
enqueueMessageB c reqs = do
|
||||
aVRange <- asks $ maxVersion . smpAgentVRange . config
|
||||
reqMids <- withStoreBatch c $ \db -> fmap (bindRight $ storeSentMsg db aVRange) reqs
|
||||
getAVRange <- asks $ smpAgentVRange . config
|
||||
reqMids <- withStoreBatch c $ \db -> fmap (bindRight $ storeSentMsg db getAVRange) reqs
|
||||
forME reqMids $ \((cData, sq :| sqs, _, _, _), InternalId msgId, pqSecr) -> do
|
||||
submitPendingMsg c cData sq
|
||||
let sqs' = filter isActiveSndQ sqs
|
||||
pure $ Right ((msgId, pqSecr), if null sqs' then Nothing else Just (cData, sqs', msgId))
|
||||
where
|
||||
storeSentMsg :: DB.Connection -> VersionSMPA -> (ConnData, NonEmpty SndQueue, Maybe CR.PQEncryption, MsgFlags, AMessage) -> IO (Either AgentErrorType ((ConnData, NonEmpty SndQueue, Maybe CR.PQEncryption, MsgFlags, AMessage), InternalId, CR.PQEncryption))
|
||||
storeSentMsg db agentVersion req@(ConnData {connId}, sq :| _, pqEnc_, msgFlags, aMessage) = fmap (first storeError) $ runExceptT $ do
|
||||
storeSentMsg :: DB.Connection -> (CR.PQEncryption -> VersionRangeSMPA) -> (ConnData, NonEmpty SndQueue, Maybe CR.PQEncryption, MsgFlags, AMessage) -> IO (Either AgentErrorType ((ConnData, NonEmpty SndQueue, Maybe CR.PQEncryption, MsgFlags, AMessage), InternalId, CR.PQEncryption))
|
||||
storeSentMsg db getAVRange req@(ConnData {connId, connAgentVersion = v}, sq :| _, pqEnc_, msgFlags, aMessage) = fmap (first storeError) $ runExceptT $ do
|
||||
internalTs <- liftIO getCurrentTime
|
||||
(internalId, internalSndId, prevMsgHash) <- liftIO $ updateSndIds db connId
|
||||
let privHeader = APrivHeader (unSndId internalSndId) prevMsgHash
|
||||
agentMsg = AgentMessage privHeader aMessage
|
||||
agentMsgStr = smpEncode agentMsg
|
||||
internalHash = C.sha256Hash agentMsgStr
|
||||
(encAgentMessage, pqEncryption) <- agentRatchetEncrypt db connId agentMsgStr e2eEncUserMsgLength pqEnc_
|
||||
let msgBody = smpEncode $ AgentMsgEnvelope {agentVersion, encAgentMessage}
|
||||
(encAgentMessage, pqEncryption) <- agentRatchetEncrypt db connId agentMsgStr (e2eEncUserMsgLength v) pqEnc_
|
||||
let agentVersion = maxVersion . getAVRange $ fromMaybe CR.PQEncOff pqEnc_
|
||||
msgBody = smpEncode $ AgentMsgEnvelope {agentVersion, encAgentMessage}
|
||||
msgType = agentMessageType agentMsg
|
||||
msgData = SndMsgData {internalId, internalSndId, internalTs, msgType, msgFlags, msgBody, pqEncryption, internalHash, prevMsgHash}
|
||||
liftIO $ createSndMsg db connId msgData
|
||||
@@ -1402,18 +1406,19 @@ abortConnectionSwitch' c connId =
|
||||
synchronizeRatchet' :: AgentMonad m => AgentClient -> ConnId -> CR.PQEncryption -> Bool -> m ConnectionStats
|
||||
synchronizeRatchet' c connId pqEnc force = withConnLock c connId "synchronizeRatchet" $ do
|
||||
withStore c (`getConn` connId) >>= \case
|
||||
SomeConn _ (DuplexConnection cData rqs sqs)
|
||||
SomeConn _ (DuplexConnection cData@ConnData {pqEncryption} rqs sqs)
|
||||
| ratchetSyncAllowed cData || force -> do
|
||||
-- check queues are not switching?
|
||||
cData' <- if pqEncryption == pqEnc then pure cData else withStore' c $ \db -> setConnPQEncryption db cData pqEnc
|
||||
AgentConfig {e2eEncryptVRange} <- asks config
|
||||
g <- asks random
|
||||
(pk1, pk2, pKem, e2eParams) <- liftIO $ CR.generateRcvE2EParams g (maxVersion e2eEncryptVRange) pqEnc
|
||||
enqueueRatchetKeyMsgs c cData sqs e2eParams
|
||||
(pk1, pk2, pKem, e2eParams) <- liftIO $ CR.generateRcvE2EParams g (maxVersion $ e2eEncryptVRange pqEnc) pqEnc
|
||||
enqueueRatchetKeyMsgs c cData' sqs e2eParams
|
||||
withStore' c $ \db -> do
|
||||
setConnRatchetSync db connId RSStarted
|
||||
setRatchetX3dhKeys db connId pk1 pk2 pKem
|
||||
let cData' = cData {ratchetSyncState = RSStarted} :: ConnData
|
||||
conn' = DuplexConnection cData' rqs sqs
|
||||
let cData'' = cData' {ratchetSyncState = RSStarted} :: ConnData
|
||||
conn' = DuplexConnection cData'' rqs sqs
|
||||
pure $ connectionStats conn'
|
||||
| otherwise -> throwError $ CMD PROHIBITED
|
||||
_ -> throwError $ CMD PROHIBITED
|
||||
@@ -2064,8 +2069,8 @@ processSMPTransmission c@AgentClient {smpClients, subQ} (tSess@(_, srv, _), _v,
|
||||
_ -> prohibited >> ack
|
||||
_ -> prohibited >> ack
|
||||
updateConnVersion :: Connection c -> ConnData -> VersionSMPA -> m (Connection c)
|
||||
updateConnVersion conn' cData' msgAgentVersion = do
|
||||
aVRange <- asks $ smpAgentVRange . config
|
||||
updateConnVersion conn' cData'@ConnData {pqEncryption} msgAgentVersion = do
|
||||
aVRange <- asks $ ($ pqEncryption) . smpAgentVRange . config
|
||||
let msgAVRange = fromMaybe (versionToRange msgAgentVersion) $ safeVersionRange (minVersion aVRange) msgAgentVersion
|
||||
case msgAVRange `compatibleVersion` aVRange of
|
||||
Just (Compatible av)
|
||||
@@ -2126,21 +2131,27 @@ processSMPTransmission c@AgentClient {smpClients, subQ} (tSess@(_, srv, _), _v,
|
||||
parseMessage :: Encoding a => ByteString -> m a
|
||||
parseMessage = liftEither . parse smpP (AGENT A_MESSAGE)
|
||||
|
||||
-- TODO PQ make sure pqEncryption in conn' is set correctly
|
||||
smpConfirmation :: SMP.MsgId -> Connection c -> C.APublicAuthKey -> C.PublicKeyX25519 -> Maybe (CR.SndE2ERatchetParams 'C.X448) -> ByteString -> VersionSMPC -> VersionSMPA -> m ()
|
||||
smpConfirmation srvMsgId conn' senderKey e2ePubKey e2eEncryption encConnInfo smpClientVersion agentVersion = do
|
||||
logServer "<--" c srv rId $ "MSG <CONF>:" <> logSecret srvMsgId
|
||||
AgentConfig {smpClientVRange, smpAgentVRange, e2eEncryptVRange} <- asks config
|
||||
let ConnData {pqEncryption} = toConnData conn'
|
||||
aVRange = smpAgentVRange pqEncryption
|
||||
e2eVRange = e2eEncryptVRange pqEncryption
|
||||
unless
|
||||
(agentVersion `isCompatible` smpAgentVRange && smpClientVersion `isCompatible` smpClientVRange)
|
||||
(agentVersion `isCompatible` aVRange && smpClientVersion `isCompatible` smpClientVRange)
|
||||
(throwError $ AGENT A_VERSION)
|
||||
case status of
|
||||
New -> case (conn', e2eEncryption) of
|
||||
-- party initiating connection
|
||||
(RcvConnection ConnData {pqEncryption} _, Just (CR.AE2ERatchetParams _ e2eSndParams@(CR.E2ERatchetParams e2eVersion _ _ _))) -> do
|
||||
unless (e2eVersion `isCompatible` e2eEncryptVRange) (throwError $ AGENT A_VERSION)
|
||||
(RcvConnection _ _, Just (CR.AE2ERatchetParams _ e2eSndParams@(CR.E2ERatchetParams e2eVersion _ _ _))) -> do
|
||||
unless (e2eVersion `isCompatible` e2eVRange) (throwError $ AGENT A_VERSION)
|
||||
(pk1, rcDHRs, pKem) <- withStore c (`getRatchetX3dhKeys` connId)
|
||||
rcParams <- liftError cryptoError $ CR.pqX3dhRcv pk1 rcDHRs pKem e2eSndParams
|
||||
let rc = CR.initRcvRatchet e2eEncryptVRange rcDHRs rcParams pqEncryption
|
||||
-- TODO PQ combine isCompatible check and construction in one call
|
||||
let rcVs = CR.RVersions {current = e2eVersion, maxSupported = maxVersion e2eVRange}
|
||||
rc = CR.initRcvRatchet rcVs rcDHRs rcParams pqEncryption
|
||||
g <- asks random
|
||||
(agentMsgBody_, rc', skipped) <- liftError cryptoError $ CR.rcDecrypt g rc M.empty encConnInfo
|
||||
case (agentMsgBody_, skipped) of
|
||||
@@ -2153,7 +2164,7 @@ processSMPTransmission c@AgentClient {smpClients, subQ} (tSess@(_, srv, _), _v,
|
||||
processConf connInfo senderConf = do
|
||||
let newConfirmation = NewConfirmation {connId, senderConf, ratchetState = rc'}
|
||||
confId <- withStore c $ \db -> do
|
||||
setConnectionVersion db connId agentVersion
|
||||
setConnAgentVersion db connId agentVersion
|
||||
createConfirmation db g newConfirmation
|
||||
let srvs = map qServer $ smpReplyQueues senderConf
|
||||
notify $ CONF confId srvs connInfo
|
||||
@@ -2182,8 +2193,6 @@ processSMPTransmission c@AgentClient {smpClients, subQ} (tSess@(_, srv, _), _v,
|
||||
-- `sndStatus == Active` when HELLO was previously sent, and this is the reply HELLO
|
||||
-- this branch is executed by the accepting party in duplexHandshake mode (v2)
|
||||
-- (was executed by initiating party in v1 that is no longer supported)
|
||||
--
|
||||
-- TODO PQ encryption mode
|
||||
| sndStatus == Active -> notify $ CON pqEncryption
|
||||
| otherwise -> enqueueDuplexHello sq
|
||||
_ -> pure ()
|
||||
@@ -2328,13 +2337,17 @@ processSMPTransmission c@AgentClient {smpClients, subQ} (tSess@(_, srv, _), _v,
|
||||
DuplexConnection {} -> action conn'
|
||||
_ -> qError $ name <> ": message must be sent to duplex connection"
|
||||
|
||||
-- TODO PQ make sure pqEncryption is set correctly here
|
||||
newRatchetKey :: CR.RcvE2ERatchetParams 'C.X448 -> Connection 'CDuplex -> m ()
|
||||
newRatchetKey e2eOtherPartyParams@(CR.E2ERatchetParams e2eVersion k1Rcv k2Rcv kem_) conn'@(DuplexConnection cData'@ConnData {lastExternalSndId} _ sqs) =
|
||||
newRatchetKey e2eOtherPartyParams@(CR.E2ERatchetParams e2eVersion k1Rcv k2Rcv _) conn'@(DuplexConnection cData'@ConnData {lastExternalSndId, pqEncryption} _ sqs) =
|
||||
unlessM ratchetExists $ do
|
||||
AgentConfig {e2eEncryptVRange} <- asks config
|
||||
unless (e2eVersion `isCompatible` e2eEncryptVRange) (throwError $ AGENT A_VERSION)
|
||||
let connE2EVRange = e2eEncryptVRange pqEncryption
|
||||
unless (e2eVersion `isCompatible` connE2EVRange) (throwError $ AGENT A_VERSION)
|
||||
keys <- getSendRatchetKeys
|
||||
initRatchet e2eEncryptVRange keys
|
||||
-- TODO PQ combine with `isCompatible` check above
|
||||
let rcVs = CR.RVersions {current = e2eVersion, maxSupported = maxVersion connE2EVRange}
|
||||
initRatchet rcVs keys
|
||||
notifyAgreed
|
||||
where
|
||||
rkHashRcv = rkHash k1Rcv k2Rcv
|
||||
@@ -2360,8 +2373,7 @@ processSMPTransmission c@AgentClient {smpClients, subQ} (tSess@(_, srv, _), _v,
|
||||
where
|
||||
sendReplyKey = do
|
||||
g <- asks random
|
||||
-- TODO PQ the decision to use KEM should depend on connection
|
||||
(pk1, pk2, pKem, e2eParams) <- liftIO $ CR.generateRcvE2EParams g e2eVersion CR.PQEncOn
|
||||
(pk1, pk2, pKem, e2eParams) <- liftIO $ CR.generateRcvE2EParams g e2eVersion pqEncryption
|
||||
enqueueRatchetKeyMsgs c cData' sqs e2eParams
|
||||
pure (pk1, pk2, pKem)
|
||||
notifyRatchetSyncError = do
|
||||
@@ -2380,16 +2392,15 @@ processSMPTransmission c@AgentClient {smpClients, subQ} (tSess@(_, srv, _), _v,
|
||||
createRatchet db connId rc
|
||||
-- compare public keys `k1` in AgentRatchetKey messages sent by self and other party
|
||||
-- to determine ratchet initilization ordering
|
||||
initRatchet :: CR.VersionRangeE2E -> (C.PrivateKeyX448, C.PrivateKeyX448, Maybe CR.RcvPrivRKEMParams) -> m ()
|
||||
initRatchet e2eEncryptVRange (pk1, pk2, pKem)
|
||||
initRatchet :: CR.RatchetVersions -> (C.PrivateKeyX448, C.PrivateKeyX448, Maybe CR.RcvPrivRKEMParams) -> m ()
|
||||
initRatchet rcVs (pk1, pk2, pKem)
|
||||
| rkHash (C.publicKey pk1) (C.publicKey pk2) <= rkHashRcv = do
|
||||
rcParams <- liftError cryptoError $ CR.pqX3dhRcv pk1 pk2 pKem e2eOtherPartyParams
|
||||
-- TODO PQ the decision to use KEM should either depend on the global setting or on whether it was enabled in connection before
|
||||
recreateRatchet $ CR.initRcvRatchet e2eEncryptVRange pk2 rcParams $ CR.PQEncryption (isJust kem_)
|
||||
recreateRatchet $ CR.initRcvRatchet rcVs pk2 rcParams pqEncryption
|
||||
| otherwise = do
|
||||
(_, rcDHRs) <- atomically . C.generateKeyPair =<< asks random
|
||||
rcParams <- liftEitherWith cryptoError $ CR.pqX3dhSnd pk1 pk2 (CR.APRKP CR.SRKSProposed <$> pKem) e2eOtherPartyParams
|
||||
recreateRatchet $ CR.initSndRatchet e2eEncryptVRange k2Rcv rcDHRs rcParams
|
||||
recreateRatchet $ CR.initSndRatchet rcVs k2Rcv rcDHRs rcParams
|
||||
void . enqueueMessages' c cData' sqs Nothing SMP.MsgFlags {notification = True} $ EREADY lastExternalSndId
|
||||
|
||||
checkMsgIntegrity :: PrevExternalSndId -> ExternalSndId -> PrevRcvMsgHash -> ByteString -> MsgIntegrity
|
||||
@@ -2432,7 +2443,7 @@ confirmQueueAsync c cData sq srv connInfo e2eEncryption_ pqEnc subMode = do
|
||||
submitPendingMsg c cData sq
|
||||
|
||||
confirmQueue :: forall m. AgentMonad m => Compatible VersionSMPA -> AgentClient -> ConnData -> SndQueue -> SMPServerWithAuth -> ConnInfo -> Maybe (CR.SndE2ERatchetParams 'C.X448) -> Maybe CR.PQEncryption -> SubscriptionMode -> m ()
|
||||
confirmQueue (Compatible agentVersion) c cData@ConnData {connId} sq srv connInfo e2eEncryption_ pqEnc_ subMode = do
|
||||
confirmQueue (Compatible agentVersion) c cData@ConnData {connId, connAgentVersion = v} sq srv connInfo e2eEncryption_ pqEnc_ subMode = do
|
||||
msg <- mkConfirmation =<< mkAgentConfirmation c cData sq srv connInfo subMode
|
||||
sendConfirmation c sq msg
|
||||
withStore' c $ \db -> setSndQueueStatus db sq Confirmed
|
||||
@@ -2440,7 +2451,7 @@ confirmQueue (Compatible agentVersion) c cData@ConnData {connId} sq srv connInfo
|
||||
mkConfirmation :: AgentMessage -> m MsgBody
|
||||
mkConfirmation aMessage = withStore c $ \db -> runExceptT $ do
|
||||
void . liftIO $ updateSndIds db connId
|
||||
(encConnInfo, _) <- agentRatchetEncrypt db connId (smpEncode aMessage) e2eEncConnInfoLength pqEnc_
|
||||
(encConnInfo, _) <- agentRatchetEncrypt db connId (smpEncode aMessage) (e2eEncConnInfoLength v) pqEnc_
|
||||
pure . smpEncode $ AgentConfirmation {agentVersion, e2eEncryption_, encConnInfo}
|
||||
|
||||
mkAgentConfirmation :: AgentMonad m => AgentClient -> ConnData -> SndQueue -> SMPServerWithAuth -> ConnInfo -> SubscriptionMode -> m AgentMessage
|
||||
@@ -2454,13 +2465,13 @@ enqueueConfirmation c cData sq connInfo e2eEncryption_ pqEnc_ = do
|
||||
submitPendingMsg c cData sq
|
||||
|
||||
storeConfirmation :: AgentMonad m => AgentClient -> ConnData -> SndQueue -> Maybe (CR.SndE2ERatchetParams 'C.X448) -> Maybe CR.PQEncryption -> AgentMessage -> m ()
|
||||
storeConfirmation c ConnData {connId, connAgentVersion} sq e2eEncryption_ pqEnc_ agentMsg = withStore c $ \db -> runExceptT $ do
|
||||
storeConfirmation c ConnData {connId, connAgentVersion = v} sq e2eEncryption_ pqEnc_ agentMsg = withStore c $ \db -> runExceptT $ do
|
||||
internalTs <- liftIO getCurrentTime
|
||||
(internalId, internalSndId, prevMsgHash) <- liftIO $ updateSndIds db connId
|
||||
let agentMsgStr = smpEncode agentMsg
|
||||
internalHash = C.sha256Hash agentMsgStr
|
||||
(encConnInfo, pqEncryption) <- agentRatchetEncrypt db connId agentMsgStr e2eEncConnInfoLength pqEnc_
|
||||
let msgBody = smpEncode $ AgentConfirmation {agentVersion = connAgentVersion, e2eEncryption_, encConnInfo}
|
||||
(encConnInfo, pqEncryption) <- agentRatchetEncrypt db connId agentMsgStr (e2eEncConnInfoLength v) pqEnc_
|
||||
let msgBody = smpEncode $ AgentConfirmation {agentVersion = v, e2eEncryption_, encConnInfo}
|
||||
msgType = agentMessageType agentMsg
|
||||
msgData = SndMsgData {internalId, internalSndId, internalTs, msgType, msgBody, pqEncryption, msgFlags = SMP.MsgFlags {notification = True}, internalHash, prevMsgHash}
|
||||
liftIO $ createSndMsg db connId msgData
|
||||
@@ -2472,8 +2483,8 @@ enqueueRatchetKeyMsgs c cData (sq :| sqs) e2eEncryption = do
|
||||
mapM_ (enqueueSavedMessage c cData msgId) $ filter isActiveSndQ sqs
|
||||
|
||||
enqueueRatchetKey :: forall m. AgentMonad m => AgentClient -> ConnData -> SndQueue -> CR.RcvE2ERatchetParams 'C.X448 -> m AgentMsgId
|
||||
enqueueRatchetKey c cData@ConnData {connId} sq e2eEncryption = do
|
||||
aVRange <- asks $ smpAgentVRange . config
|
||||
enqueueRatchetKey c cData@ConnData {connId, pqEncryption} sq e2eEncryption = do
|
||||
aVRange <- asks $ ($ pqEncryption) . smpAgentVRange . config
|
||||
msgId <- storeRatchetKey $ maxVersion aVRange
|
||||
submitPendingMsg c cData sq
|
||||
pure $ unId msgId
|
||||
@@ -2487,8 +2498,7 @@ enqueueRatchetKey c cData@ConnData {connId} sq e2eEncryption = do
|
||||
internalHash = C.sha256Hash agentMsgStr
|
||||
let msgBody = smpEncode $ AgentRatchetKey {agentVersion, e2eEncryption, info = agentMsgStr}
|
||||
msgType = agentMessageType agentMsg
|
||||
-- TODO PQ set pqEncryption based on connection mode
|
||||
msgData = SndMsgData {internalId, internalSndId, internalTs, msgType, msgBody, pqEncryption = CR.PQEncOff, msgFlags = SMP.MsgFlags {notification = True}, internalHash, prevMsgHash}
|
||||
msgData = SndMsgData {internalId, internalSndId, internalTs, msgType, msgBody, pqEncryption, msgFlags = SMP.MsgFlags {notification = True}, internalHash, prevMsgHash}
|
||||
liftIO $ createSndMsg db connId msgData
|
||||
liftIO $ createSndMsgDelivery db connId sq internalId
|
||||
pure internalId
|
||||
|
||||
@@ -56,15 +56,14 @@ import qualified Simplex.Messaging.Agent.Store.SQLite.Migrations as Migrations
|
||||
import Simplex.Messaging.Client
|
||||
import Simplex.Messaging.Client.Agent ()
|
||||
import qualified Simplex.Messaging.Crypto as C
|
||||
import Simplex.Messaging.Crypto.Ratchet (VersionRangeE2E, supportedE2EEncryptVRange)
|
||||
import Simplex.Messaging.Crypto.Ratchet (PQEncryption, VersionRangeE2E, supportedE2EEncryptVRange)
|
||||
import Simplex.Messaging.Notifications.Client (defaultNTFClientConfig)
|
||||
import Simplex.Messaging.Notifications.Transport (NTFVersion)
|
||||
import Simplex.Messaging.Notifications.Types
|
||||
import Simplex.Messaging.Protocol (NtfServer, VersionRangeSMPC, XFTPServer, XFTPServerWithAuth, supportedSMPClientVRange)
|
||||
import Simplex.Messaging.Transport (SMPVersion)
|
||||
import Simplex.Messaging.TMap (TMap)
|
||||
import qualified Simplex.Messaging.TMap as TM
|
||||
import Simplex.Messaging.Transport (TLS, Transport (..))
|
||||
import Simplex.Messaging.Transport (SMPVersion, TLS, Transport (..))
|
||||
import Simplex.Messaging.Transport.Client (defaultSMPPort)
|
||||
import Simplex.Messaging.Util (allFinally, catchAllErrors, tryAllErrors)
|
||||
import System.Random (StdGen, newStdGen)
|
||||
@@ -117,8 +116,8 @@ data AgentConfig = AgentConfig
|
||||
caCertificateFile :: FilePath,
|
||||
privateKeyFile :: FilePath,
|
||||
certificateFile :: FilePath,
|
||||
e2eEncryptVRange :: VersionRangeE2E,
|
||||
smpAgentVRange :: VersionRangeSMPA,
|
||||
e2eEncryptVRange :: PQEncryption -> VersionRangeE2E,
|
||||
smpAgentVRange :: PQEncryption -> VersionRangeSMPA,
|
||||
smpClientVRange :: VersionRangeSMPC
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ module Simplex.Messaging.Agent.Protocol
|
||||
pattern VersionSMPA,
|
||||
ratchetSyncSMPAgentVersion,
|
||||
deliveryRcptsSMPAgentVersion,
|
||||
pqdrSMPAgentVersion,
|
||||
supportedSMPAgentVRange,
|
||||
e2eEncConnInfoLength,
|
||||
e2eEncUserMsgLength,
|
||||
@@ -187,7 +188,15 @@ import Simplex.FileTransfer.Protocol (FileParty (..))
|
||||
import Simplex.FileTransfer.Transport (XFTPErrorType)
|
||||
import Simplex.Messaging.Agent.QueryString
|
||||
import qualified Simplex.Messaging.Crypto as C
|
||||
import Simplex.Messaging.Crypto.Ratchet (InitialKeys (..), PQEncryption (..), pattern PQEncOff, RcvE2ERatchetParams, RcvE2ERatchetParamsUri, SndE2ERatchetParams)
|
||||
import Simplex.Messaging.Crypto.Ratchet
|
||||
( InitialKeys (..),
|
||||
PQEncryption (..),
|
||||
pattern PQEncOff,
|
||||
pattern PQEncOn,
|
||||
RcvE2ERatchetParams,
|
||||
RcvE2ERatchetParamsUri,
|
||||
SndE2ERatchetParams
|
||||
)
|
||||
import Simplex.Messaging.Encoding
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Parsers
|
||||
@@ -234,6 +243,7 @@ import UnliftIO.Exception (Exception)
|
||||
-- 2 - "duplex" (more efficient) connection handshake (6/9/2022)
|
||||
-- 3 - support ratchet renegotiation (6/30/2023)
|
||||
-- 4 - delivery receipts (7/13/2023)
|
||||
-- 5 - post-quantum double ratchet (3/14/2024)
|
||||
|
||||
data SMPAgentVersion
|
||||
|
||||
@@ -255,24 +265,34 @@ ratchetSyncSMPAgentVersion = VersionSMPA 3
|
||||
deliveryRcptsSMPAgentVersion :: VersionSMPA
|
||||
deliveryRcptsSMPAgentVersion = VersionSMPA 4
|
||||
|
||||
pqdrSMPAgentVersion :: VersionSMPA
|
||||
pqdrSMPAgentVersion = VersionSMPA 5
|
||||
|
||||
-- TODO v5.7 increase to 5
|
||||
currentSMPAgentVersion :: VersionSMPA
|
||||
currentSMPAgentVersion = VersionSMPA 4
|
||||
|
||||
supportedSMPAgentVRange :: VersionRangeSMPA
|
||||
supportedSMPAgentVRange = mkVersionRange duplexHandshakeSMPAgentVersion currentSMPAgentVersion
|
||||
-- TODO v5.7 remove dependency of version range on whether PQ encryption is used
|
||||
supportedSMPAgentVRange :: PQEncryption -> VersionRangeSMPA
|
||||
supportedSMPAgentVRange pq =
|
||||
mkVersionRange duplexHandshakeSMPAgentVersion $ case pq of
|
||||
PQEncOn -> pqdrSMPAgentVersion
|
||||
PQEncOff -> currentSMPAgentVersion
|
||||
|
||||
-- it is shorter to allow all handshake headers,
|
||||
-- including E2E (double-ratchet) parameters and
|
||||
-- signing key of the sender for the server
|
||||
-- TODO PQ this should be version-dependent
|
||||
-- previously it was 14848, reduced by 3700 (roughly the increase of message ratchet header size + key and ciphertext in reply link)
|
||||
e2eEncConnInfoLength :: Int
|
||||
e2eEncConnInfoLength = 11148
|
||||
e2eEncConnInfoLength :: VersionSMPA -> Int
|
||||
e2eEncConnInfoLength v
|
||||
-- reduced by 3700 (roughly the increase of message ratchet header size + key and ciphertext in reply link)
|
||||
| v >= pqdrSMPAgentVersion = 11148
|
||||
| otherwise = 14848
|
||||
|
||||
-- TODO PQ this should be version-dependent
|
||||
-- previously it was 15856, reduced by 2200 (roughly the increase of message ratchet header size)
|
||||
e2eEncUserMsgLength :: Int
|
||||
e2eEncUserMsgLength = 13656
|
||||
e2eEncUserMsgLength :: VersionSMPA -> Int
|
||||
e2eEncUserMsgLength v
|
||||
-- reduced by 2200 (roughly the increase of message ratchet header size)
|
||||
| v >= pqdrSMPAgentVersion = 13656
|
||||
| otherwise = 15856
|
||||
|
||||
-- | Raw (unparsed) SMP agent protocol transmission.
|
||||
type ARawTransmission = (ByteString, ByteString, ByteString)
|
||||
|
||||
@@ -58,6 +58,7 @@ module Simplex.Messaging.Agent.Store.SQLite
|
||||
getConnData,
|
||||
setConnDeleted,
|
||||
setConnAgentVersion,
|
||||
setConnPQEncryption,
|
||||
getDeletedConnIds,
|
||||
getDeletedWaitingDeliveryConnIds,
|
||||
setConnRatchetSync,
|
||||
@@ -93,7 +94,6 @@ module Simplex.Messaging.Agent.Store.SQLite
|
||||
getAcceptedConfirmation,
|
||||
removeConfirmations,
|
||||
-- Invitations - sent via Contact connections
|
||||
setConnectionVersion,
|
||||
createInvitation,
|
||||
getInvitation,
|
||||
acceptInvitation,
|
||||
@@ -889,10 +889,6 @@ removeConfirmations db connId =
|
||||
|]
|
||||
[":conn_id" := connId]
|
||||
|
||||
setConnectionVersion :: DB.Connection -> ConnId -> VersionSMPA -> IO ()
|
||||
setConnectionVersion db connId aVersion =
|
||||
DB.execute db "UPDATE connections SET smp_agent_version = ? WHERE conn_id = ?" (aVersion, connId)
|
||||
|
||||
createInvitation :: DB.Connection -> TVar ChaChaDRG -> NewInvitation -> IO (Either StoreError InvitationId)
|
||||
createInvitation db gVar NewInvitation {contactConnId, connReq, recipientConnInfo} =
|
||||
createWithRandomId gVar $ \invitationId ->
|
||||
@@ -1956,6 +1952,11 @@ setConnAgentVersion :: DB.Connection -> ConnId -> VersionSMPA -> IO ()
|
||||
setConnAgentVersion db connId aVersion =
|
||||
DB.execute db "UPDATE connections SET smp_agent_version = ? WHERE conn_id = ?" (aVersion, connId)
|
||||
|
||||
setConnPQEncryption :: DB.Connection -> ConnData -> CR.PQEncryption -> IO ConnData
|
||||
setConnPQEncryption db cData@ConnData {connId} pqEnc = do
|
||||
DB.execute db "UPDATE connections SET pq_encryption = ? WHERE conn_id = ?" (pqEnc, connId)
|
||||
pure (cData :: ConnData) {pqEncryption = pqEnc}
|
||||
|
||||
getDeletedConnIds :: DB.Connection -> IO [ConnId]
|
||||
getDeletedConnIds db = map fromOnly <$> DB.query db "SELECT conn_id FROM connections WHERE deleted = ?" (Only True)
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ module Simplex.Messaging.Crypto.Ratchet
|
||||
SkippedMsgDiff (..),
|
||||
SkippedMsgKeys,
|
||||
InitialKeys (..),
|
||||
pattern IKPQOn,
|
||||
pattern IKPQOff,
|
||||
PQEncryption (..),
|
||||
pattern PQEncOn,
|
||||
pattern PQEncOff,
|
||||
@@ -40,8 +42,9 @@ module Simplex.Messaging.Crypto.Ratchet
|
||||
VersionE2E,
|
||||
VersionRangeE2E,
|
||||
pattern VersionE2E,
|
||||
RatchetVersions (..),
|
||||
kdfX3DHE2EEncryptVersion,
|
||||
pqRatchetVersion,
|
||||
pqRatchetE2EEncryptVersion,
|
||||
currentE2EEncryptVersion,
|
||||
supportedE2EEncryptVRange,
|
||||
generateRcvE2EParams,
|
||||
@@ -58,7 +61,6 @@ module Simplex.Messaging.Crypto.Ratchet
|
||||
rcDecrypt,
|
||||
-- used in tests
|
||||
MsgHeader (..),
|
||||
RatchetVersions (..),
|
||||
RatchetInitParams (..),
|
||||
UseKEM (..),
|
||||
RKEMParams (..),
|
||||
@@ -71,6 +73,8 @@ module Simplex.Messaging.Crypto.Ratchet
|
||||
ratchetVersions,
|
||||
fullHeaderLen,
|
||||
applySMDiff,
|
||||
encodeMsgHeader,
|
||||
msgHeaderP,
|
||||
)
|
||||
where
|
||||
|
||||
@@ -85,7 +89,7 @@ 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)
|
||||
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)
|
||||
@@ -131,14 +135,19 @@ pattern VersionE2E v = Version v
|
||||
kdfX3DHE2EEncryptVersion :: VersionE2E
|
||||
kdfX3DHE2EEncryptVersion = VersionE2E 2
|
||||
|
||||
pqRatchetVersion :: VersionE2E
|
||||
pqRatchetVersion = VersionE2E 3
|
||||
pqRatchetE2EEncryptVersion :: VersionE2E
|
||||
pqRatchetE2EEncryptVersion = VersionE2E 3
|
||||
|
||||
-- TODO v5.7 increase to 3
|
||||
currentE2EEncryptVersion :: VersionE2E
|
||||
currentE2EEncryptVersion = VersionE2E 3
|
||||
currentE2EEncryptVersion = VersionE2E 2
|
||||
|
||||
supportedE2EEncryptVRange :: VersionRangeE2E
|
||||
supportedE2EEncryptVRange = mkVersionRange kdfX3DHE2EEncryptVersion currentE2EEncryptVersion
|
||||
-- TODO v5.7 remove dependency of version range on whether PQ encryption is used
|
||||
supportedE2EEncryptVRange :: PQEncryption -> VersionRangeE2E
|
||||
supportedE2EEncryptVRange pq =
|
||||
mkVersionRange kdfX3DHE2EEncryptVersion $ case pq of
|
||||
PQEncOn -> pqRatchetE2EEncryptVersion
|
||||
PQEncOff -> currentE2EEncryptVersion
|
||||
|
||||
data RatchetKEMState
|
||||
= RKSProposed -- only KEM encapsulation key
|
||||
@@ -215,7 +224,7 @@ deriving instance Show AnyE2ERatchetParams
|
||||
|
||||
instance (RatchetKEMStateI s, AlgorithmI a) => Encoding (E2ERatchetParams s a) where
|
||||
smpEncode (E2ERatchetParams v k1 k2 kem_)
|
||||
| v >= pqRatchetVersion = smpEncode (v, k1, k2, kem_)
|
||||
| v >= pqRatchetE2EEncryptVersion = smpEncode (v, k1, k2, kem_)
|
||||
| otherwise = smpEncode (v, k1, k2)
|
||||
smpP = toParams <$?> smpP
|
||||
where
|
||||
@@ -243,7 +252,7 @@ instance Encoding AnyE2ERatchetParams where
|
||||
where
|
||||
kemP :: VersionE2E -> Parser (Maybe ARKEMParams)
|
||||
kemP v
|
||||
| v >= pqRatchetVersion = smpP
|
||||
| v >= pqRatchetE2EEncryptVersion = smpP
|
||||
| otherwise = pure Nothing
|
||||
|
||||
instance VersionI E2EVersion (E2ERatchetParams s a) where
|
||||
@@ -283,7 +292,7 @@ instance (RatchetKEMStateI s, AlgorithmI a) => StrEncoding (E2ERatchetParamsUri
|
||||
<> maybe [] encodeKem kem_
|
||||
where
|
||||
encodeKem kem
|
||||
| maxVersion vs < pqRatchetVersion = []
|
||||
| maxVersion vs < pqRatchetE2EEncryptVersion = []
|
||||
| otherwise = case kem of
|
||||
RKParamsProposed k -> [("kem_key", strEncode k)]
|
||||
RKParamsAccepted ct k -> [("kem_ct", strEncode ct), ("kem_key", strEncode k)]
|
||||
@@ -313,7 +322,7 @@ instance StrEncoding AnyE2ERatchetParamsUri where
|
||||
_ -> fail "bad e2e params"
|
||||
where
|
||||
kemP vr query
|
||||
| maxVersion vr >= pqRatchetVersion =
|
||||
| maxVersion vr >= pqRatchetE2EEncryptVersion =
|
||||
queryParam_ "kem_key" query
|
||||
$>>= \k -> Just . kemParams k <$> queryParam_ "kem_ct" query
|
||||
| otherwise = pure Nothing
|
||||
@@ -366,7 +375,7 @@ generateE2EParams g v useKEM_ = do
|
||||
where
|
||||
kemParams :: IO (Maybe (RKEMParams s, PrivRKEMParams s))
|
||||
kemParams = case useKEM_ of
|
||||
Just useKem | v >= pqRatchetVersion -> Just <$> do
|
||||
Just useKem | v >= pqRatchetE2EEncryptVersion -> Just <$> do
|
||||
ks@(k, _) <- sntrup761Keypair g
|
||||
case useKem of
|
||||
ProposeKEM -> pure (RKParamsProposed k, PrivateRKParamsProposed ks)
|
||||
@@ -414,7 +423,7 @@ pqX3dhSnd spk1 spk2 spKem_ (E2ERatchetParams v rk1 rk2 rKem_) = do
|
||||
where
|
||||
sndPq :: Either CryptoError (Maybe KEMKeyPair, Maybe RatchetKEMAccepted)
|
||||
sndPq = case spKem_ of
|
||||
Just (APRKP _ ps) | v >= pqRatchetVersion -> case (ps, rKem_) of
|
||||
Just (APRKP _ ps) | v >= pqRatchetE2EEncryptVersion -> 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
|
||||
@@ -430,7 +439,7 @@ pqX3dhRcv rpk1 rpk2 rpKem_ (E2ERatchetParams v sk1 sk2 sKem_) = do
|
||||
where
|
||||
rcvPq :: ExceptT CryptoError IO (Maybe (KEMKeyPair, RatchetKEMAccepted))
|
||||
rcvPq = case sKem_ of
|
||||
Just (RKParamsAccepted ct k') | v >= pqRatchetVersion -> case rpKem_ of
|
||||
Just (RKParamsAccepted ct k') | v >= pqRatchetE2EEncryptVersion -> case rpKem_ of
|
||||
Just (PrivateRKParamsProposed ks@(_, pk)) -> do
|
||||
shared <- liftIO $ sntrup761Dec ct pk
|
||||
pure $ Just (ks, RatchetKEMAccepted k' shared ct)
|
||||
@@ -457,7 +466,6 @@ data Ratchet a = Ratchet
|
||||
rcAD :: Str,
|
||||
rcDHRs :: PrivateKey a,
|
||||
rcKEM :: Maybe RatchetKEM,
|
||||
-- TODO PQ make them optional via JSON parser for PQEncryption
|
||||
rcEnableKEM :: PQEncryption, -- will enable KEM on the next ratchet step
|
||||
rcSndKEM :: PQEncryption, -- used KEM hybrid secret for sending ratchet
|
||||
rcRcvKEM :: PQEncryption, -- used KEM hybrid secret for receiving ratchet
|
||||
@@ -584,12 +592,12 @@ instance FromField MessageKey where fromField = blobFieldDecoder smpDecode
|
||||
-- // above added for KEM
|
||||
-- @
|
||||
initSndRatchet ::
|
||||
forall a. (AlgorithmI a, DhAlgorithm a) => VersionRangeE2E -> PublicKey a -> PrivateKey a -> (RatchetInitParams, Maybe KEMKeyPair) -> Ratchet a
|
||||
initSndRatchet v rcDHRr rcDHRs (RatchetInitParams {assocData, ratchetKey, sndHK, rcvNextHK, kemAccepted}, rcPQRs_) = do
|
||||
forall a. (AlgorithmI a, DhAlgorithm a) => RatchetVersions -> PublicKey a -> PrivateKey a -> (RatchetInitParams, Maybe KEMKeyPair) -> Ratchet a
|
||||
initSndRatchet rcVersion rcDHRr rcDHRs (RatchetInitParams {assocData, ratchetKey, sndHK, rcvNextHK, kemAccepted}, rcPQRs_) = do
|
||||
-- state.RK, state.CKs, state.NHKs = KDF_RK_HE(SK, DH(state.DHRs, state.DHRr) || state.PQRss)
|
||||
let (rcRK, rcCKs, rcNHKs) = rootKdf ratchetKey rcDHRr rcDHRs (rcPQRss <$> kemAccepted)
|
||||
in Ratchet
|
||||
{ rcVersion = ratchetVersions v,
|
||||
{ rcVersion,
|
||||
rcAD = assocData,
|
||||
rcDHRs,
|
||||
rcKEM = (`RatchetKEM` kemAccepted) <$> rcPQRs_,
|
||||
@@ -613,10 +621,10 @@ initSndRatchet v rcDHRr rcDHRs (RatchetInitParams {assocData, ratchetKey, sndHK,
|
||||
-- Please note that the public part of rcDHRs was sent to the sender
|
||||
-- as part of the connection request and random salt was received from the sender.
|
||||
initRcvRatchet ::
|
||||
forall a. (AlgorithmI a, DhAlgorithm a) => VersionRangeE2E -> PrivateKey a -> (RatchetInitParams, Maybe KEMKeyPair) -> PQEncryption -> Ratchet a
|
||||
initRcvRatchet v rcDHRs (RatchetInitParams {assocData, ratchetKey, sndHK, rcvNextHK, kemAccepted}, rcPQRs_) rcEnableKEM =
|
||||
forall a. (AlgorithmI a, DhAlgorithm a) => RatchetVersions -> PrivateKey a -> (RatchetInitParams, Maybe KEMKeyPair) -> PQEncryption -> Ratchet a
|
||||
initRcvRatchet rcVersion rcDHRs (RatchetInitParams {assocData, ratchetKey, sndHK, rcvNextHK, kemAccepted}, rcPQRs_) rcEnableKEM =
|
||||
Ratchet
|
||||
{ rcVersion = ratchetVersions v,
|
||||
{ rcVersion,
|
||||
rcAD = assocData,
|
||||
rcDHRs,
|
||||
-- rcKEM:
|
||||
@@ -654,56 +662,69 @@ data MsgHeader a = MsgHeader
|
||||
-- 69 = 2 (original size) + 2 + 1+56 (Curve448) + 4 + 4
|
||||
-- TODO PQ this must be version-dependent
|
||||
-- TODO this is the exact size, some reserve should be added
|
||||
paddedHeaderLen :: Int
|
||||
paddedHeaderLen = 2284
|
||||
paddedHeaderLen :: VersionE2E -> Int
|
||||
paddedHeaderLen v
|
||||
| v >= pqRatchetE2EEncryptVersion = 2284
|
||||
| otherwise = 88
|
||||
|
||||
-- only used in tests to validate correct padding
|
||||
-- (2 bytes - version size, 1 byte - header size, not to have it fixed or version-dependent)
|
||||
fullHeaderLen :: Int
|
||||
fullHeaderLen = 2 + 1 + paddedHeaderLen + authTagSize + ivSize @AES256
|
||||
fullHeaderLen :: VersionE2E -> Int
|
||||
fullHeaderLen v = 2 + 1 + paddedHeaderLen v + authTagSize + ivSize @AES256
|
||||
|
||||
instance AlgorithmI a => Encoding (MsgHeader a) where
|
||||
smpEncode MsgHeader {msgMaxVersion, msgDHRs, msgKEM, msgPN, msgNs}
|
||||
| msgMaxVersion >= pqRatchetVersion = smpEncode (msgMaxVersion, msgDHRs, msgKEM, msgPN, msgNs)
|
||||
| otherwise = smpEncode (msgMaxVersion, msgDHRs, msgPN, msgNs)
|
||||
smpP = do
|
||||
msgMaxVersion <- smpP
|
||||
msgDHRs <- smpP
|
||||
msgKEM <- if msgMaxVersion >= pqRatchetVersion then smpP else pure Nothing
|
||||
msgPN <- smpP
|
||||
msgNs <- smpP
|
||||
pure MsgHeader {msgMaxVersion, msgDHRs, msgKEM, msgPN, msgNs}
|
||||
-- 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}
|
||||
|
||||
data EncMessageHeader = EncMessageHeader
|
||||
{ ehVersion :: VersionE2E,
|
||||
{ ehVersion :: VersionE2E, -- this is current ratchet version
|
||||
ehIV :: IV,
|
||||
ehAuthTag :: AuthTag,
|
||||
ehBody :: ByteString
|
||||
}
|
||||
|
||||
-- this encoding depends on version in EncMessageHeader because it is "current" ratchet version
|
||||
instance Encoding EncMessageHeader where
|
||||
smpEncode EncMessageHeader {ehVersion, ehIV, ehAuthTag, ehBody}
|
||||
| ehVersion >= pqRatchetVersion = smpEncode (ehVersion, ehIV, ehAuthTag, Large ehBody)
|
||||
| ehVersion >= pqRatchetE2EEncryptVersion = smpEncode (ehVersion, ehIV, ehAuthTag, Large ehBody)
|
||||
| otherwise = smpEncode (ehVersion, ehIV, ehAuthTag, ehBody)
|
||||
smpP = do
|
||||
(ehVersion, ehIV, ehAuthTag) <- smpP
|
||||
ehBody <- if ehVersion >= pqRatchetVersion then unLarge <$> smpP else smpP
|
||||
ehBody <- if ehVersion >= pqRatchetE2EEncryptVersion then unLarge <$> smpP else smpP
|
||||
pure EncMessageHeader {ehVersion, ehIV, ehAuthTag, ehBody}
|
||||
|
||||
-- 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,
|
||||
emAuthTag :: AuthTag,
|
||||
emBody :: ByteString
|
||||
}
|
||||
|
||||
-- the encoder always uses 2-byte lengths for the new version, even for short headers without PQ keys.
|
||||
encodeEncRatchetMessage :: VersionE2E -> EncRatchetMessage -> ByteString
|
||||
encodeEncRatchetMessage v EncRatchetMessage {emHeader, emBody, emAuthTag}
|
||||
| v >= pqRatchetVersion = smpEncode (Large emHeader, emAuthTag, Tail emBody)
|
||||
| v >= pqRatchetE2EEncryptVersion = smpEncode (Large emHeader, emAuthTag, Tail emBody)
|
||||
| otherwise = smpEncode (emHeader, emAuthTag, Tail emBody)
|
||||
|
||||
encRatchetMessageP :: VersionE2E -> Parser EncRatchetMessage
|
||||
encRatchetMessageP v = do
|
||||
emHeader <- if v >= pqRatchetVersion then unLarge <$> smpP else smpP
|
||||
-- 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.
|
||||
encRatchetMessageP :: Parser EncRatchetMessage
|
||||
encRatchetMessageP = do
|
||||
len1 <- peekWord8'
|
||||
emHeader <- if len1 < 32 then unLarge <$> smpP else smpP
|
||||
(emAuthTag, Tail emBody) <- smpP
|
||||
pure EncRatchetMessage {emHeader, emBody, emAuthTag}
|
||||
|
||||
@@ -724,6 +745,7 @@ instance ToJSON PQEncryption where
|
||||
|
||||
instance FromJSON PQEncryption where
|
||||
parseJSON v = PQEncryption <$> parseJSON v
|
||||
omittedField = Just PQEncOff
|
||||
|
||||
replyKEM_ :: PQEncryption -> Maybe (RKEMParams 'RKSProposed) -> Maybe AUseKEM
|
||||
replyKEM_ pqEnc kem_ = case pqEnc of
|
||||
@@ -747,6 +769,12 @@ instance StrEncoding PQEncryption where
|
||||
data InitialKeys = IKUsePQ | IKNoPQ PQEncryption
|
||||
deriving (Eq, Show)
|
||||
|
||||
pattern IKPQOn :: InitialKeys
|
||||
pattern IKPQOn = IKNoPQ PQEncOn
|
||||
|
||||
pattern IKPQOff :: InitialKeys
|
||||
pattern IKPQOff = IKNoPQ PQEncOff
|
||||
|
||||
instance StrEncoding InitialKeys where
|
||||
strEncode = \case
|
||||
IKUsePQ -> "pq=invitation"
|
||||
@@ -773,25 +801,30 @@ joinContactInitialKeys = \case
|
||||
|
||||
rcEncrypt :: AlgorithmI a => Ratchet a -> Int -> ByteString -> Maybe PQEncryption -> ExceptT CryptoError IO (ByteString, Ratchet a)
|
||||
rcEncrypt Ratchet {rcSnd = Nothing} _ _ _ = throwE CERatchetState
|
||||
rcEncrypt rc@Ratchet {rcSnd = Just sr@SndRatchet {rcCKs, rcHKs}, rcDHRs, rcKEM, rcNs, rcPN, rcAD = Str rcAD, rcVersion} paddedMsgLen msg pqMode_ = do
|
||||
rcEncrypt rc@Ratchet {rcSnd = Just sr@SndRatchet {rcCKs, rcHKs}, rcDHRs, rcKEM, rcNs, rcPN, rcAD = Str rcAD, rcVersion} paddedMsgLen msg pqEnc_ = do
|
||||
-- state.CKs, mk = KDF_CK(state.CKs)
|
||||
let (ck', mk, iv, ehIV) = chainKdf rcCKs
|
||||
-- enc_header = HENCRYPT(state.HKs, header)
|
||||
(ehAuthTag, ehBody) <- encryptAEAD rcHKs ehIV paddedHeaderLen rcAD msgHeader
|
||||
let v = current rcVersion
|
||||
(ehAuthTag, ehBody) <- encryptAEAD rcHKs ehIV (paddedHeaderLen v) rcAD (msgHeader v)
|
||||
-- return enc_header, ENCRYPT(mk, plaintext, CONCAT(AD, enc_header))
|
||||
-- TODO PQ versioning in Ratchet should change: we should use "current" version here
|
||||
let emHeader = smpEncode EncMessageHeader {ehVersion = maxSupported rcVersion, ehBody, ehAuthTag, ehIV}
|
||||
let emHeader = smpEncode EncMessageHeader {ehVersion = v, ehBody, ehAuthTag, ehIV}
|
||||
(emAuthTag, emBody) <- encryptAEAD mk iv paddedMsgLen (rcAD <> emHeader) msg
|
||||
let msg' = encodeEncRatchetMessage (maxSupported rcVersion) EncRatchetMessage {emHeader, emBody, emAuthTag}
|
||||
let msg' = encodeEncRatchetMessage v EncRatchetMessage {emHeader, emBody, emAuthTag}
|
||||
-- state.Ns += 1
|
||||
rc' = rc {rcSnd = Just sr {rcCKs = ck'}, rcNs = rcNs + 1}
|
||||
rc'' = case pqMode_ of
|
||||
-- TODO v5.8 remove comments below
|
||||
-- Note that maxSupported will not downgrade here below current.
|
||||
-- TODO v5.7 remove comments below
|
||||
-- It will downgrade when decrypting the message when the current version downgrades to remove support for PQ encryption.
|
||||
-- TODO v5.8 replace `max v currentE2EEncryptVersion` with `v` (to allow downgrade when app downgraded)
|
||||
rc' = rc {rcSnd = Just sr {rcCKs = ck'}, rcNs = rcNs + 1, rcVersion = rcVersion {maxSupported = max v currentE2EEncryptVersion}}
|
||||
rc'' = case pqEnc_ of
|
||||
Nothing -> rc'
|
||||
Just rcEnableKEM
|
||||
| enablePQ rcEnableKEM -> rc' {rcEnableKEM}
|
||||
| otherwise ->
|
||||
let rcKEM' = (\rck -> rck {rcKEMs = Nothing}) <$> rcKEM
|
||||
in rc' {rcEnableKEM, rcKEM = rcKEM'}
|
||||
-- This sets max version to support PQ encryption.
|
||||
-- Current version upgrade happens when peer decrypts the message.
|
||||
-- TODO v5.7 remove version upgrade here, as it's already upgraded above
|
||||
Just PQEncOn -> rc' {rcEnableKEM = PQEncOn, rcVersion = rcVersion {maxSupported = pqRatchetE2EEncryptVersion}}
|
||||
Just PQEncOff -> rc' {rcEnableKEM = PQEncOff, rcKEM = (\rck -> rck {rcKEMs = Nothing}) <$> rcKEM}
|
||||
pure (msg', rc'')
|
||||
where
|
||||
-- header = HEADER_PQ2(
|
||||
@@ -801,8 +834,9 @@ rcEncrypt rc@Ratchet {rcSnd = Just sr@SndRatchet {rcCKs, rcHKs}, rcDHRs, rcKEM,
|
||||
-- pn = state.PN,
|
||||
-- n = state.Ns
|
||||
-- )
|
||||
msgHeader =
|
||||
smpEncode
|
||||
msgHeader v =
|
||||
encodeMsgHeader
|
||||
v
|
||||
MsgHeader
|
||||
{ msgMaxVersion = maxSupported rcVersion,
|
||||
msgDHRs = publicKey rcDHRs,
|
||||
@@ -837,7 +871,7 @@ rcDecrypt ::
|
||||
ExceptT CryptoError IO (DecryptResult a)
|
||||
rcDecrypt g rc@Ratchet {rcRcv, rcAD = Str rcAD, rcVersion} rcMKSkipped msg' = do
|
||||
-- TODO PQ versioning should change
|
||||
encMsg@EncRatchetMessage {emHeader} <- parseE CryptoHeaderError (encRatchetMessageP $ maxSupported rcVersion) msg'
|
||||
encMsg@EncRatchetMessage {emHeader} <- parseE CryptoHeaderError encRatchetMessageP msg'
|
||||
encHdr <- parseE CryptoHeaderError smpP emHeader
|
||||
-- plaintext = TrySkippedMessageKeysHE(state, enc_header, cipher-text, AD)
|
||||
decryptSkipped encHdr encMsg >>= \case
|
||||
@@ -851,9 +885,16 @@ rcDecrypt g rc@Ratchet {rcRcv, rcAD = Str rcAD, rcVersion} rcMKSkipped msg' = do
|
||||
SMMessage r -> pure r
|
||||
where
|
||||
decryptRcMessage :: RatchetStep -> MsgHeader a -> EncRatchetMessage -> ExceptT CryptoError IO (DecryptResult a)
|
||||
decryptRcMessage rcStep MsgHeader {msgDHRs, msgKEM, msgPN, msgNs} encMsg = do
|
||||
decryptRcMessage rcStep hdr@MsgHeader {msgMaxVersion, msgPN, msgNs} encMsg = do
|
||||
-- if dh_ratchet:
|
||||
(rc', smks1) <- ratchetStep rcStep
|
||||
(rc', smks1) <- case rcStep of
|
||||
SameRatchet -> pure (upgradedRatchet, M.empty)
|
||||
AdvanceRatchet -> do
|
||||
-- SkipMessageKeysHE(state, header.pn)
|
||||
(rc', hmks) <- liftEither $ skipMessageKeys msgPN upgradedRatchet
|
||||
-- DHRatchetPQ2HE(state, header)
|
||||
(,hmks) <$> ratchetStep rc' hdr
|
||||
-- SkipMessageKeysHE(state, header.n)
|
||||
case skipMessageKeys msgNs rc' of
|
||||
Left e -> pure (Left e, rc', smkDiff smks1)
|
||||
Right (rc''@Ratchet {rcRcv = Just rr@RcvRatchet {rcCKr}, rcNr}, smks2) -> do
|
||||
@@ -863,47 +904,44 @@ rcDecrypt g rc@Ratchet {rcRcv, rcAD = Str rcAD, rcVersion} rcMKSkipped msg' = do
|
||||
msg <- decryptMessage (MessageKey mk iv) encMsg
|
||||
-- state . Nr += 1
|
||||
pure (msg, rc'' {rcRcv = Just rr {rcCKr = rcCKr'}, rcNr = rcNr + 1}, smkDiff $ smks1 <> smks2)
|
||||
Right (rc'', smks2) -> do
|
||||
Right (rc'', smks2) ->
|
||||
pure (Left CERatchetState, rc'', smkDiff $ smks1 <> smks2)
|
||||
where
|
||||
upgradedRatchet :: Ratchet a
|
||||
upgradedRatchet
|
||||
| msgMaxVersion > current rcVersion = rc {rcVersion = rcVersion {current = min msgMaxVersion $ maxSupported rcVersion}}
|
||||
| otherwise = rc
|
||||
smkDiff :: SkippedMsgKeys -> SkippedMsgDiff
|
||||
smkDiff smks = if M.null smks then SMDNoChange else SMDAdd smks
|
||||
ratchetStep :: RatchetStep -> ExceptT CryptoError IO (Ratchet a, SkippedMsgKeys)
|
||||
ratchetStep SameRatchet = pure (rc, M.empty)
|
||||
ratchetStep AdvanceRatchet =
|
||||
-- SkipMessageKeysHE(state, header.pn)
|
||||
case skipMessageKeys msgPN rc of
|
||||
Left e -> throwE e
|
||||
Right (rc'@Ratchet {rcDHRs, rcRK, rcNHKs, rcNHKr}, hmks) -> do
|
||||
-- DHRatchetPQ2HE(state, header)
|
||||
(kemSS, kemSS', rcKEM') <- pqRatchetStep rc' msgKEM
|
||||
-- state.DHRs = GENERATE_DH()
|
||||
(_, rcDHRs') <- atomically $ generateKeyPair @a g
|
||||
-- state.RK, state.CKr, state.NHKr = KDF_RK_HE(state.RK, DH(state.DHRs, state.DHRr) || ss)
|
||||
let (rcRK', rcCKr', rcNHKr') = rootKdf rcRK msgDHRs rcDHRs kemSS
|
||||
-- state.RK, state.CKs, state.NHKs = KDF_RK_HE(state.RK, DH(state.DHRs, state.DHRr) || state.PQRss)
|
||||
(rcRK'', rcCKs', rcNHKs') = rootKdf rcRK' msgDHRs rcDHRs' kemSS'
|
||||
sndKEM = isJust kemSS'
|
||||
rcvKEM = isJust kemSS
|
||||
rc'' =
|
||||
rc'
|
||||
{ rcDHRs = rcDHRs',
|
||||
rcKEM = rcKEM',
|
||||
rcEnableKEM = PQEncryption $ sndKEM || rcvKEM,
|
||||
rcSndKEM = PQEncryption sndKEM,
|
||||
rcRcvKEM = PQEncryption rcvKEM,
|
||||
rcRK = rcRK'',
|
||||
rcSnd = Just SndRatchet {rcDHRr = msgDHRs, rcCKs = rcCKs', rcHKs = rcNHKs},
|
||||
rcRcv = Just RcvRatchet {rcCKr = rcCKr', rcHKr = rcNHKr},
|
||||
rcPN = rcNs rc,
|
||||
rcNs = 0,
|
||||
rcNr = 0,
|
||||
rcNHKs = rcNHKs',
|
||||
rcNHKr = rcNHKr'
|
||||
}
|
||||
pure (rc'', hmks)
|
||||
ratchetStep :: Ratchet a -> MsgHeader a -> ExceptT CryptoError IO (Ratchet a)
|
||||
ratchetStep rc'@Ratchet {rcDHRs, rcRK, rcNHKs, rcNHKr} MsgHeader {msgDHRs, msgKEM} = do
|
||||
(kemSS, kemSS', rcKEM') <- pqRatchetStep rc' msgKEM
|
||||
-- state.DHRs = GENERATE_DH()
|
||||
(_, rcDHRs') <- atomically $ generateKeyPair @a g
|
||||
-- state.RK, state.CKr, state.NHKr = KDF_RK_HE(state.RK, DH(state.DHRs, state.DHRr) || ss)
|
||||
let (rcRK', rcCKr', rcNHKr') = rootKdf rcRK msgDHRs rcDHRs kemSS
|
||||
-- state.RK, state.CKs, state.NHKs = KDF_RK_HE(state.RK, DH(state.DHRs, state.DHRr) || state.PQRss)
|
||||
(rcRK'', rcCKs', rcNHKs') = rootKdf rcRK' msgDHRs rcDHRs' kemSS'
|
||||
sndKEM = isJust kemSS'
|
||||
rcvKEM = isJust kemSS
|
||||
pure
|
||||
rc'
|
||||
{ rcDHRs = rcDHRs',
|
||||
rcKEM = rcKEM',
|
||||
rcEnableKEM = PQEncryption $ sndKEM || rcvKEM,
|
||||
rcSndKEM = PQEncryption sndKEM,
|
||||
rcRcvKEM = PQEncryption rcvKEM,
|
||||
rcRK = rcRK'',
|
||||
rcSnd = Just SndRatchet {rcDHRr = msgDHRs, rcCKs = rcCKs', rcHKs = rcNHKs},
|
||||
rcRcv = Just RcvRatchet {rcCKr = rcCKr', rcHKr = rcNHKr},
|
||||
rcPN = rcNs rc,
|
||||
rcNs = 0,
|
||||
rcNr = 0,
|
||||
rcNHKs = rcNHKs',
|
||||
rcNHKr = rcNHKr'
|
||||
}
|
||||
pqRatchetStep :: Ratchet a -> Maybe ARKEMParams -> ExceptT CryptoError IO (Maybe KEMSharedKey, Maybe KEMSharedKey, Maybe RatchetKEM)
|
||||
pqRatchetStep Ratchet {rcKEM, rcEnableKEM = PQEncryption pqEnc} = \case
|
||||
pqRatchetStep Ratchet {rcKEM, rcEnableKEM = PQEncryption pqEnc, rcVersion = rv} = \case
|
||||
-- received message does not have KEM in header,
|
||||
-- but the user enabled KEM when sending previous message
|
||||
Nothing -> case rcKEM of
|
||||
@@ -913,7 +951,7 @@ rcDecrypt g rc@Ratchet {rcRcv, rcAD = Str rcAD, rcVersion} rcMKSkipped msg' = do
|
||||
_ -> pure (Nothing, Nothing, Nothing)
|
||||
-- received message has KEM in header.
|
||||
Just (ARKP _ ps)
|
||||
| pqEnc -> do
|
||||
| pqEnc && current rv >= pqRatchetE2EEncryptVersion -> do
|
||||
-- state.PQRr = header.kem
|
||||
(ss, rcPQRr) <- sharedSecret
|
||||
-- state.PQRct = PQKEM-ENC(state.PQRr, state.PQRss) // encapsulated additional shared secret KEM #1
|
||||
@@ -981,9 +1019,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 {ehBody, ehAuthTag, ehIV} = do
|
||||
decryptHeader k EncMessageHeader {ehVersion, ehBody, ehAuthTag, ehIV} = do
|
||||
header <- decryptAEAD k ehIV rcAD ehBody ehAuthTag `catchE` \_ -> throwE CERatchetHeader
|
||||
parseE' CryptoHeaderError smpP header
|
||||
parseE' CryptoHeaderError (msgHeaderP ehVersion) 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))
|
||||
|
||||
+16
-22
@@ -29,7 +29,7 @@ import SMPAgentClient
|
||||
import SMPClient (testKeyHash, testPort, testPort2, testStoreLogFile, withSmpServer, withSmpServerStoreLogOn)
|
||||
import Simplex.Messaging.Agent.Protocol hiding (MID)
|
||||
import qualified Simplex.Messaging.Agent.Protocol as A
|
||||
import Simplex.Messaging.Crypto.Ratchet (InitialKeys (..), PQEncryption (..), pattern PQEncOn, pattern PQEncOff)
|
||||
import Simplex.Messaging.Crypto.Ratchet (InitialKeys (..), PQEncryption (..), pattern IKPQOn, pattern IKPQOff, pattern PQEncOn, pattern PQEncOff)
|
||||
import qualified Simplex.Messaging.Crypto.Ratchet as CR
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Protocol (ErrorType (..))
|
||||
@@ -184,10 +184,10 @@ pqMatrix2NoInv = pqMatrix2_ False
|
||||
|
||||
pqMatrix2_ :: Bool -> PQMatrix2 c
|
||||
pqMatrix2_ pqInv _ smpTest test = do
|
||||
it "dh/dh handshake" $ smpTest $ \a b -> test (a, ikPQOff) (b, PQEncOff)
|
||||
it "dh/pq handshake" $ smpTest $ \a b -> test (a, ikPQOff) (b, PQEncOn)
|
||||
it "pq/dh handshake" $ smpTest $ \a b -> test (a, ikPQOn) (b, PQEncOff)
|
||||
it "pq/pq handshake" $ smpTest $ \a b -> test (a, ikPQOn) (b, PQEncOn)
|
||||
it "dh/dh handshake" $ smpTest $ \a b -> test (a, IKPQOff) (b, PQEncOff)
|
||||
it "dh/pq handshake" $ smpTest $ \a b -> test (a, IKPQOff) (b, PQEncOn)
|
||||
it "pq/dh handshake" $ smpTest $ \a b -> test (a, IKPQOn) (b, PQEncOff)
|
||||
it "pq/pq handshake" $ smpTest $ \a b -> test (a, IKPQOn) (b, PQEncOn)
|
||||
when pqInv $ do
|
||||
it "pq-inv/dh handshake" $ smpTest $ \a b -> test (a, IKUsePQ) (b, PQEncOff)
|
||||
it "pq-inv/pq handshake" $ smpTest $ \a b -> test (a, IKUsePQ) (b, PQEncOn)
|
||||
@@ -199,17 +199,17 @@ pqMatrix3 ::
|
||||
(HasCallStack => (c, InitialKeys) -> (c, PQEncryption) -> (c, PQEncryption) -> IO ()) ->
|
||||
Spec
|
||||
pqMatrix3 _ smpTest test = do
|
||||
it "dh" $ smpTest $ \a b c -> test (a, ikPQOff) (b, PQEncOff) (c, PQEncOff)
|
||||
it "dh/dh/pq" $ smpTest $ \a b c -> test (a, ikPQOff) (b, PQEncOff) (c, PQEncOn)
|
||||
it "dh/pq/dh" $ smpTest $ \a b c -> test (a, ikPQOff) (b, PQEncOn) (c, PQEncOff)
|
||||
it "dh/pq/pq" $ smpTest $ \a b c -> test (a, ikPQOff) (b, PQEncOn) (c, PQEncOn)
|
||||
it "pq/dh/dh" $ smpTest $ \a b c -> test (a, ikPQOn) (b, PQEncOff) (c, PQEncOff)
|
||||
it "pq/dh/pq" $ smpTest $ \a b c -> test (a, ikPQOn) (b, PQEncOff) (c, PQEncOn)
|
||||
it "pq/pq/dh" $ smpTest $ \a b c -> test (a, ikPQOn) (b, PQEncOn) (c, PQEncOff)
|
||||
it "pq" $ smpTest $ \a b c -> test (a, ikPQOn) (b, PQEncOn) (c, PQEncOn)
|
||||
it "dh" $ smpTest $ \a b c -> test (a, IKPQOff) (b, PQEncOff) (c, PQEncOff)
|
||||
it "dh/dh/pq" $ smpTest $ \a b c -> test (a, IKPQOff) (b, PQEncOff) (c, PQEncOn)
|
||||
it "dh/pq/dh" $ smpTest $ \a b c -> test (a, IKPQOff) (b, PQEncOn) (c, PQEncOff)
|
||||
it "dh/pq/pq" $ smpTest $ \a b c -> test (a, IKPQOff) (b, PQEncOn) (c, PQEncOn)
|
||||
it "pq/dh/dh" $ smpTest $ \a b c -> test (a, IKPQOn) (b, PQEncOff) (c, PQEncOff)
|
||||
it "pq/dh/pq" $ smpTest $ \a b c -> test (a, IKPQOn) (b, PQEncOff) (c, PQEncOn)
|
||||
it "pq/pq/dh" $ smpTest $ \a b c -> test (a, IKPQOn) (b, PQEncOn) (c, PQEncOff)
|
||||
it "pq" $ smpTest $ \a b c -> test (a, IKPQOn) (b, PQEncOn) (c, PQEncOn)
|
||||
|
||||
testDuplexConnection :: (HasCallStack, Transport c) => TProxy c -> c -> c -> IO ()
|
||||
testDuplexConnection _ alice bob = testDuplexConnection' (alice, ikPQOn) (bob, PQEncOn)
|
||||
testDuplexConnection _ alice bob = testDuplexConnection' (alice, IKPQOn) (bob, PQEncOn)
|
||||
|
||||
testDuplexConnection' :: (HasCallStack, Transport c) => (c, InitialKeys) -> (c, PQEncryption) -> IO ()
|
||||
testDuplexConnection' (alice, aPQ) (bob, bPQ) = do
|
||||
@@ -246,7 +246,7 @@ testDuplexConnection' (alice, aPQ) (bob, bPQ) = do
|
||||
alice #:# "nothing else should be delivered to alice"
|
||||
|
||||
testDuplexConnRandomIds :: (HasCallStack, Transport c) => TProxy c -> c -> c -> IO ()
|
||||
testDuplexConnRandomIds _ alice bob = testDuplexConnRandomIds' (alice, ikPQOn) (bob, PQEncOn)
|
||||
testDuplexConnRandomIds _ alice bob = testDuplexConnRandomIds' (alice, IKPQOn) (bob, PQEncOn)
|
||||
|
||||
testDuplexConnRandomIds' :: (HasCallStack, Transport c) => (c, InitialKeys) -> (c, PQEncryption) -> IO ()
|
||||
testDuplexConnRandomIds' (alice, aPQ) (bob, bPQ) = do
|
||||
@@ -546,14 +546,8 @@ testResumeDeliveryQuotaExceeded _ alice bob = do
|
||||
-- message 8 is skipped because of alice agent sending "QCONT" message
|
||||
bob #: ("5", "alice", "ACK 9") #> ("5", "alice", OK)
|
||||
|
||||
ikPQOn :: InitialKeys
|
||||
ikPQOn = IKNoPQ PQEncOn
|
||||
|
||||
ikPQOff :: InitialKeys
|
||||
ikPQOff = IKNoPQ PQEncOff
|
||||
|
||||
connect :: Transport c => (c, ByteString) -> (c, ByteString) -> IO ()
|
||||
connect (h1, name1) (h2, name2) = connect' (h1, name1, ikPQOn) (h2, name2, PQEncOn)
|
||||
connect (h1, name1) (h2, name2) = connect' (h1, name1, IKPQOn) (h2, name2, PQEncOn)
|
||||
|
||||
connect' :: forall c. Transport c => (c, ByteString, InitialKeys) -> (c, ByteString, PQEncryption) -> IO ()
|
||||
connect' (h1, name1, pqMode1) (h2, name2, pqMode2) = do
|
||||
|
||||
@@ -81,7 +81,7 @@ testE2ERatchetParams :: RcvE2ERatchetParamsUri 'C.X448
|
||||
testE2ERatchetParams = E2ERatchetParamsUri (mkVersionRange (VersionE2E 1) (VersionE2E 1)) testDhPubKey testDhPubKey Nothing
|
||||
|
||||
testE2ERatchetParams12 :: RcvE2ERatchetParamsUri 'C.X448
|
||||
testE2ERatchetParams12 = E2ERatchetParamsUri supportedE2EEncryptVRange testDhPubKey testDhPubKey Nothing
|
||||
testE2ERatchetParams12 = E2ERatchetParamsUri (supportedE2EEncryptVRange PQEncOn) testDhPubKey testDhPubKey Nothing
|
||||
|
||||
connectionRequest :: AConnectionRequestUri
|
||||
connectionRequest =
|
||||
@@ -95,7 +95,7 @@ connectionRequestCurrentRange :: AConnectionRequestUri
|
||||
connectionRequestCurrentRange =
|
||||
ACR SCMInvitation $
|
||||
CRInvitationUri
|
||||
connReqData {crAgentVRange = supportedSMPAgentVRange, crSmpQueues = [queueV1, queueV1]}
|
||||
connReqData {crAgentVRange = supportedSMPAgentVRange PQEncOn, crSmpQueues = [queueV1, queueV1]}
|
||||
testE2ERatchetParams12
|
||||
|
||||
connectionRequestClientDataEmpty :: AConnectionRequestUri
|
||||
@@ -135,7 +135,7 @@ connectionRequestTests =
|
||||
<> urlEncode True testDhKeyStrUri
|
||||
<> "&e2e=v%3D1%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D"
|
||||
strEncode connectionRequestCurrentRange
|
||||
`shouldBe` "simplex:/invitation#/?v=2-4&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1%26dh%3D"
|
||||
`shouldBe` "simplex:/invitation#/?v=2-5&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1%26dh%3D"
|
||||
<> urlEncode True testDhKeyStrUri
|
||||
<> "%2Csmp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1%26dh%3D"
|
||||
<> urlEncode True testDhKeyStrUri
|
||||
@@ -185,7 +185,7 @@ connectionRequestTests =
|
||||
<> testDhKeyStrUri
|
||||
<> "&e2e=extra_key%3Dnew%26v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D"
|
||||
<> "&some_new_param=abc"
|
||||
<> "&v=2-4"
|
||||
<> "&v=2-5"
|
||||
)
|
||||
`shouldBe` Right connectionRequestCurrentRange
|
||||
strDecode
|
||||
|
||||
@@ -39,12 +39,13 @@ doubleRatchetTests = do
|
||||
describe "double-ratchet encryption/decryption" $ do
|
||||
it "should serialize and parse message header" $ do
|
||||
testAlgs $ testMessageHeader kdfX3DHE2EEncryptVersion
|
||||
testAlgs $ testMessageHeader $ max pqRatchetVersion currentE2EEncryptVersion
|
||||
testAlgs $ testMessageHeader $ max pqRatchetE2EEncryptVersion currentE2EEncryptVersion
|
||||
describe "message tests" $ runMessageTests initRatchets False
|
||||
it "should encode/decode ratchet as JSON" $ do
|
||||
testAlgs testKeyJSON
|
||||
testAlgs testRatchetJSON
|
||||
testVersionJSON
|
||||
it "should decode v2 Ratchet with default field values" $ testDecodeV2RatchetJSON
|
||||
it "should agree the same ratchet parameters" $ testAlgs testX3dh
|
||||
it "should agree the same ratchet parameters with version 1" $ testAlgs testX3dhV1
|
||||
describe "post-quantum hybrid KEM double-ratchet algorithm" $ do
|
||||
@@ -89,15 +90,15 @@ paddedMsgLen :: Int
|
||||
paddedMsgLen = 100
|
||||
|
||||
fullMsgLen :: VersionE2E -> Int
|
||||
fullMsgLen v = headerLenLength + fullHeaderLen + C.authTagSize + paddedMsgLen
|
||||
fullMsgLen v = headerLenLength + fullHeaderLen v + C.authTagSize + paddedMsgLen
|
||||
where
|
||||
headerLenLength = if v < pqRatchetVersion then 1 else 3 -- two bytes are added because of two Large used in new encoding
|
||||
headerLenLength = if v < pqRatchetE2EEncryptVersion then 1 else 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 (smpP @(MsgHeader a)) (smpEncode hdr) `shouldBe` Right hdr
|
||||
parseAll (msgHeaderP v) (encodeMsgHeader v hdr) `shouldBe` Right hdr
|
||||
|
||||
testKEMParams :: Expectation
|
||||
testKEMParams = do
|
||||
@@ -115,15 +116,15 @@ testMessageHeaderKEM _ = do
|
||||
g <- C.newRandom
|
||||
(k, _) <- atomically $ C.generateKeyPair @a g
|
||||
(kem, _) <- sntrup761Keypair g
|
||||
let msgMaxVersion = max pqRatchetVersion currentE2EEncryptVersion
|
||||
let msgMaxVersion = max pqRatchetE2EEncryptVersion currentE2EEncryptVersion
|
||||
msgKEM = Just . ARKP SRKSProposed $ RKParamsProposed kem
|
||||
hdr = MsgHeader {msgMaxVersion, msgDHRs = k, msgKEM, msgPN = 0, msgNs = 0}
|
||||
parseAll (smpP @(MsgHeader a)) (smpEncode hdr) `shouldBe` Right hdr
|
||||
parseAll (msgHeaderP msgMaxVersion) (encodeMsgHeader msgMaxVersion hdr) `shouldBe` Right hdr
|
||||
(kem', _) <- sntrup761Keypair g
|
||||
(ct, _) <- sntrup761Enc g kem
|
||||
let msgKEM' = Just . ARKP SRKSAccepted $ RKParamsAccepted ct kem'
|
||||
hdr' = MsgHeader {msgMaxVersion, msgDHRs = k, msgKEM = msgKEM', msgPN = 0, msgNs = 0}
|
||||
parseAll (smpP @(MsgHeader a)) (smpEncode hdr') `shouldBe` Right hdr'
|
||||
parseAll (msgHeaderP msgMaxVersion) (encodeMsgHeader msgMaxVersion hdr') `shouldBe` Right hdr'
|
||||
|
||||
pattern Decrypted :: ByteString -> Either CryptoError (Either CryptoError ByteString)
|
||||
pattern Decrypted msg <- Right (Right msg)
|
||||
@@ -350,6 +351,14 @@ testVersionJSON = do
|
||||
testDecodeRV :: ToJSON a => a -> Expectation
|
||||
testDecodeRV a = J.eitherDecode' (J.encode a) `shouldBe` Right (rv 1 2)
|
||||
|
||||
testDecodeV2RatchetJSON :: IO ()
|
||||
testDecodeV2RatchetJSON = do
|
||||
let v2RatchetJSON = "{\"rcVersion\":[2,2],\"rcAD\":\"2GEJrq48TmQse6NR16I-hrI0tSySZQ57E_g46nDceAPRAiF6j0drq26RTE7be6X7uiB4RaGJGf4QRXzcYuVtWw==\",\"rcDHRs\":\"TUM0Q0FRQXdCUVlESzJWdUJDSUVJRkNYbUxtSHQ3SUNfeHpGTi1Qb3ZqTVQ3S2p6XzZlZlBjOG9fRFY2RWxKOQ==\",\"rcRK\":\"BOX2X7YW5qDSp2XknY_lqacSrtDqQNPvS6iJlZIs3G0=\",\"rcNs\":0,\"rcNr\":0,\"rcPN\":0,\"rcNHKs\":\"IMouSkXUvzT_mo0WM-pqEUK09-HTLk9WOTCFQglyQxU=\",\"rcNHKr\":\"g-tus1clYPV0rGlzkf5a959tUqDYQVZ1FpcPeXdKwxI=\"}"
|
||||
Right (r :: Ratchet X25519) <- pure $ J.eitherDecodeStrict' v2RatchetJSON
|
||||
rcEnableKEM r `shouldBe` PQEncOff
|
||||
rcSndKEM r `shouldBe` PQEncOff
|
||||
rcRcvKEM r `shouldBe` PQEncOff
|
||||
|
||||
testEncodeDecode :: (Eq a, Show a, ToJSON a, FromJSON a) => a -> Expectation
|
||||
testEncodeDecode x = do
|
||||
let j = J.encode x
|
||||
@@ -359,7 +368,7 @@ testEncodeDecode x = do
|
||||
testX3dh :: forall a. (AlgorithmI a, DhAlgorithm a) => C.SAlgorithm a -> IO ()
|
||||
testX3dh _ = do
|
||||
g <- C.newRandom
|
||||
let v = max pqRatchetVersion currentE2EEncryptVersion
|
||||
let v = max pqRatchetE2EEncryptVersion currentE2EEncryptVersion
|
||||
(pkBob1, pkBob2, Nothing, AE2ERatchetParams _ e2eBob) <- liftIO $ generateSndE2EParams @a g v Nothing
|
||||
(pkAlice1, pkAlice2, Nothing, e2eAlice) <- liftIO $ generateRcvE2EParams @a g v PQEncOff
|
||||
let paramsBob = pqX3dhSnd pkBob1 pkBob2 Nothing e2eAlice
|
||||
@@ -378,7 +387,7 @@ testX3dhV1 _ = do
|
||||
testPqX3dhProposeInReply :: forall a. (AlgorithmI a, DhAlgorithm a) => C.SAlgorithm a -> IO ()
|
||||
testPqX3dhProposeInReply _ = do
|
||||
g <- C.newRandom
|
||||
let v = max pqRatchetVersion currentE2EEncryptVersion
|
||||
let v = max pqRatchetE2EEncryptVersion currentE2EEncryptVersion
|
||||
-- initiate (no KEM)
|
||||
(pkAlice1, pkAlice2, Nothing, e2eAlice) <- liftIO $ generateRcvE2EParams @a g v PQEncOff
|
||||
-- propose KEM in reply
|
||||
@@ -390,7 +399,7 @@ testPqX3dhProposeInReply _ = do
|
||||
testPqX3dhProposeAccept :: forall a. (AlgorithmI a, DhAlgorithm a) => C.SAlgorithm a -> IO ()
|
||||
testPqX3dhProposeAccept _ = do
|
||||
g <- C.newRandom
|
||||
let v = max pqRatchetVersion currentE2EEncryptVersion
|
||||
let v = max pqRatchetE2EEncryptVersion currentE2EEncryptVersion
|
||||
-- initiate (propose KEM)
|
||||
(pkAlice1, pkAlice2, pKemAlice_@(Just _), e2eAlice) <- liftIO $ generateRcvE2EParams @a g v PQEncOn
|
||||
E2ERatchetParams _ _ _ (Just (RKParamsProposed aliceKem)) <- pure e2eAlice
|
||||
@@ -403,7 +412,7 @@ testPqX3dhProposeAccept _ = do
|
||||
testPqX3dhProposeReject :: forall a. (AlgorithmI a, DhAlgorithm a) => C.SAlgorithm a -> IO ()
|
||||
testPqX3dhProposeReject _ = do
|
||||
g <- C.newRandom
|
||||
let v = max pqRatchetVersion currentE2EEncryptVersion
|
||||
let v = max pqRatchetE2EEncryptVersion currentE2EEncryptVersion
|
||||
-- initiate (propose KEM)
|
||||
(pkAlice1, pkAlice2, pKemAlice_@(Just _), e2eAlice) <- liftIO $ generateRcvE2EParams @a g v PQEncOn
|
||||
E2ERatchetParams _ _ _ (Just (RKParamsProposed _)) <- pure e2eAlice
|
||||
@@ -416,7 +425,7 @@ testPqX3dhProposeReject _ = do
|
||||
testPqX3dhAcceptWithoutProposalError :: forall a. (AlgorithmI a, DhAlgorithm a) => C.SAlgorithm a -> IO ()
|
||||
testPqX3dhAcceptWithoutProposalError _ = do
|
||||
g <- C.newRandom
|
||||
let v = max pqRatchetVersion currentE2EEncryptVersion
|
||||
let v = max pqRatchetE2EEncryptVersion currentE2EEncryptVersion
|
||||
-- initiate (no KEM)
|
||||
(pkAlice1, pkAlice2, Nothing, e2eAlice) <- liftIO $ generateRcvE2EParams @a g v PQEncOff
|
||||
E2ERatchetParams _ _ _ Nothing <- pure e2eAlice
|
||||
@@ -430,7 +439,7 @@ testPqX3dhAcceptWithoutProposalError _ = do
|
||||
testPqX3dhProposeAgain :: forall a. (AlgorithmI a, DhAlgorithm a) => C.SAlgorithm a -> IO ()
|
||||
testPqX3dhProposeAgain _ = do
|
||||
g <- C.newRandom
|
||||
let v = max pqRatchetVersion currentE2EEncryptVersion
|
||||
let v = max pqRatchetE2EEncryptVersion currentE2EEncryptVersion
|
||||
-- initiate (propose KEM)
|
||||
(pkAlice1, pkAlice2, pKemAlice_@(Just _), e2eAlice) <- liftIO $ generateRcvE2EParams @a g v PQEncOn
|
||||
E2ERatchetParams _ _ _ (Just (RKParamsProposed _)) <- pure e2eAlice
|
||||
@@ -452,9 +461,9 @@ compatibleRatchets
|
||||
_ -> expectationFailure "RatchetInitParams params are not compatible"
|
||||
|
||||
encryptDecrypt :: (AlgorithmI a, DhAlgorithm a) => Maybe PQEncryption -> (Ratchet a -> ()) -> (Ratchet a -> ()) -> EncryptDecryptSpec a
|
||||
encryptDecrypt pqEnc invalidSnd invalidRcv (alice, msg) bob = do
|
||||
Right msg' <- withTVar (encrypt_ pqEnc) invalidSnd alice msg
|
||||
Decrypted msg'' <- decrypt' invalidRcv bob msg'
|
||||
encryptDecrypt pqEnc validSnd validRcv (alice, msg) bob = do
|
||||
Right msg' <- withTVar (encrypt_ pqEnc) validSnd alice msg
|
||||
Decrypted msg'' <- decrypt' validRcv bob msg'
|
||||
msg'' `shouldBe` msg
|
||||
|
||||
-- enable KEM (currently disabled)
|
||||
@@ -493,20 +502,21 @@ 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 pqRatchetVersion currentE2EEncryptVersion
|
||||
let v = max pqRatchetE2EEncryptVersion currentE2EEncryptVersion
|
||||
(pkBob1, pkBob2, _pKemParams@Nothing, AE2ERatchetParams _ e2eBob) <- liftIO $ generateSndE2EParams g v Nothing
|
||||
(pkAlice1, pkAlice2, _pKem@Nothing, e2eAlice) <- liftIO $ generateRcvE2EParams g v PQEncOff
|
||||
Right paramsBob <- pure $ pqX3dhSnd pkBob1 pkBob2 Nothing e2eAlice
|
||||
Right paramsAlice <- runExceptT $ pqX3dhRcv pkAlice1 pkAlice2 Nothing e2eBob
|
||||
(_, pkBob3) <- atomically $ C.generateKeyPair g
|
||||
let bob = initSndRatchet supportedE2EEncryptVRange (C.publicKey pkAlice2) pkBob3 paramsBob
|
||||
alice = initRcvRatchet supportedE2EEncryptVRange pkAlice2 paramsAlice PQEncOff
|
||||
let vs = testRatchetVersions PQEncOff
|
||||
bob = initSndRatchet vs (C.publicKey pkAlice2) pkBob3 paramsBob
|
||||
alice = initRcvRatchet vs pkAlice2 paramsAlice PQEncOff
|
||||
pure (alice, bob, encrypt' noSndKEM, decrypt' noRcvKEM, (\#>))
|
||||
|
||||
initRatchetsKEMProposed :: forall a. (AlgorithmI a, DhAlgorithm a) => IO (Ratchet a, Ratchet a, Encrypt a, Decrypt a, EncryptDecryptSpec a)
|
||||
initRatchetsKEMProposed = do
|
||||
g <- C.newRandom
|
||||
let v = max pqRatchetVersion currentE2EEncryptVersion
|
||||
let v = max pqRatchetE2EEncryptVersion currentE2EEncryptVersion
|
||||
-- initiate (no KEM)
|
||||
(pkAlice1, pkAlice2, Nothing, e2eAlice) <- liftIO $ generateRcvE2EParams g v PQEncOff
|
||||
-- propose KEM in reply
|
||||
@@ -515,14 +525,15 @@ initRatchetsKEMProposed = do
|
||||
Right paramsBob <- pure $ pqX3dhSnd pkBob1 pkBob2 pKemParams_ e2eAlice
|
||||
Right paramsAlice <- runExceptT $ pqX3dhRcv pkAlice1 pkAlice2 Nothing e2eBob
|
||||
(_, pkBob3) <- atomically $ C.generateKeyPair g
|
||||
let bob = initSndRatchet supportedE2EEncryptVRange (C.publicKey pkAlice2) pkBob3 paramsBob
|
||||
alice = initRcvRatchet supportedE2EEncryptVRange pkAlice2 paramsAlice PQEncOn
|
||||
let vs = testRatchetVersions PQEncOn
|
||||
bob = initSndRatchet vs (C.publicKey pkAlice2) pkBob3 paramsBob
|
||||
alice = initRcvRatchet vs pkAlice2 paramsAlice PQEncOn
|
||||
pure (alice, bob, encrypt' hasSndKEM, decrypt' hasRcvKEM, (!#>))
|
||||
|
||||
initRatchetsKEMAccepted :: forall a. (AlgorithmI a, DhAlgorithm a) => IO (Ratchet a, Ratchet a, Encrypt a, Decrypt a, EncryptDecryptSpec a)
|
||||
initRatchetsKEMAccepted = do
|
||||
g <- C.newRandom
|
||||
let v = max pqRatchetVersion currentE2EEncryptVersion
|
||||
let v = max pqRatchetE2EEncryptVersion currentE2EEncryptVersion
|
||||
-- initiate (propose)
|
||||
(pkAlice1, pkAlice2, pKem_@(Just _), e2eAlice) <- liftIO $ generateRcvE2EParams g v PQEncOn
|
||||
E2ERatchetParams _ _ _ (Just (RKParamsProposed aliceKem)) <- pure e2eAlice
|
||||
@@ -532,14 +543,15 @@ initRatchetsKEMAccepted = do
|
||||
Right paramsBob <- pure $ pqX3dhSnd pkBob1 pkBob2 pKemParams_ e2eAlice
|
||||
Right paramsAlice <- runExceptT $ pqX3dhRcv pkAlice1 pkAlice2 pKem_ e2eBob
|
||||
(_, pkBob3) <- atomically $ C.generateKeyPair g
|
||||
let bob = initSndRatchet supportedE2EEncryptVRange (C.publicKey pkAlice2) pkBob3 paramsBob
|
||||
alice = initRcvRatchet supportedE2EEncryptVRange pkAlice2 paramsAlice PQEncOn
|
||||
let vs = testRatchetVersions PQEncOn
|
||||
bob = initSndRatchet vs (C.publicKey pkAlice2) pkBob3 paramsBob
|
||||
alice = initRcvRatchet vs pkAlice2 paramsAlice PQEncOn
|
||||
pure (alice, bob, encrypt' hasSndKEM, decrypt' hasRcvKEM, (!#>))
|
||||
|
||||
initRatchetsKEMProposedAgain :: forall a. (AlgorithmI a, DhAlgorithm a) => IO (Ratchet a, Ratchet a, Encrypt a, Decrypt a, EncryptDecryptSpec a)
|
||||
initRatchetsKEMProposedAgain = do
|
||||
g <- C.newRandom
|
||||
let v = max pqRatchetVersion currentE2EEncryptVersion
|
||||
let v = max pqRatchetE2EEncryptVersion currentE2EEncryptVersion
|
||||
-- initiate (propose KEM)
|
||||
(pkAlice1, pkAlice2, pKem_@(Just _), e2eAlice) <- liftIO $ generateRcvE2EParams g v PQEncOn
|
||||
-- propose KEM again in reply
|
||||
@@ -548,10 +560,16 @@ initRatchetsKEMProposedAgain = do
|
||||
Right paramsBob <- pure $ pqX3dhSnd pkBob1 pkBob2 pKemParams_ e2eAlice
|
||||
Right paramsAlice <- runExceptT $ pqX3dhRcv pkAlice1 pkAlice2 pKem_ e2eBob
|
||||
(_, pkBob3) <- atomically $ C.generateKeyPair g
|
||||
let bob = initSndRatchet supportedE2EEncryptVRange (C.publicKey pkAlice2) pkBob3 paramsBob
|
||||
alice = initRcvRatchet supportedE2EEncryptVRange pkAlice2 paramsAlice PQEncOn
|
||||
let vs = testRatchetVersions PQEncOn
|
||||
bob = initSndRatchet vs (C.publicKey pkAlice2) pkBob3 paramsBob
|
||||
alice = initRcvRatchet vs pkAlice2 paramsAlice PQEncOn
|
||||
pure (alice, bob, encrypt' hasSndKEM, decrypt' hasRcvKEM, (!#>))
|
||||
|
||||
testRatchetVersions :: PQEncryption -> RatchetVersions
|
||||
testRatchetVersions pq =
|
||||
let v = maxVersion $ supportedE2EEncryptVRange pq
|
||||
in RVersions v v
|
||||
|
||||
encrypt_ :: AlgorithmI a => Maybe PQEncryption -> (TVar ChaChaDRG, Ratchet a, SkippedMsgKeys) -> ByteString -> IO (Either CryptoError (ByteString, Ratchet a, SkippedMsgDiff))
|
||||
encrypt_ enableKem (_, rc, _) msg =
|
||||
-- print msg >>
|
||||
@@ -559,7 +577,7 @@ encrypt_ enableKem (_, rc, _) msg =
|
||||
>>= either (pure . Left) checkLength
|
||||
where
|
||||
checkLength (msg', rc') = do
|
||||
B.length msg' `shouldBe` fullMsgLen (maxSupported $ rcVersion rc)
|
||||
B.length msg' `shouldBe` fullMsgLen (current $ rcVersion rc)
|
||||
pure $ Right (msg', rc', SMDNoChange)
|
||||
|
||||
decrypt_ :: (AlgorithmI a, DhAlgorithm a) => (TVar ChaChaDRG, Ratchet a, SkippedMsgKeys) -> ByteString -> IO (Either CryptoError (Either CryptoError ByteString, Ratchet a, SkippedMsgDiff))
|
||||
|
||||
@@ -63,11 +63,11 @@ import qualified Database.SQLite.Simple as SQL
|
||||
import SMPAgentClient
|
||||
import SMPClient (cfg, testPort, testPort2, testStoreLogFile2, withSmpServer, withSmpServerConfigOn, withSmpServerOn, withSmpServerStoreLogOn, withSmpServerStoreMsgLogOn, withSmpServerV7)
|
||||
import Simplex.Messaging.Agent hiding (createConnection, joinConnection, sendMessage)
|
||||
import qualified Simplex.Messaging.Agent as Agent
|
||||
import qualified Simplex.Messaging.Agent as A
|
||||
import Simplex.Messaging.Agent.Client (ProtocolTestFailure (..), ProtocolTestStep (..))
|
||||
import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..), InitialAgentServers (..), createAgentStore)
|
||||
import Simplex.Messaging.Agent.Protocol hiding (CON)
|
||||
import qualified Simplex.Messaging.Agent.Protocol as Agent
|
||||
import qualified Simplex.Messaging.Agent.Protocol as A
|
||||
import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation (..), SQLiteStore (dbNew))
|
||||
import Simplex.Messaging.Agent.Store.SQLite.Common (withTransaction')
|
||||
import Simplex.Messaging.Client (NetworkConfig (..), ProtocolClientConfig (..), TransportSessionMode (TSMEntity, TSMUser), defaultSMPClientConfig)
|
||||
@@ -145,7 +145,7 @@ pGet c = do
|
||||
_ -> pure t
|
||||
|
||||
pattern CON :: ACommand 'Agent 'AEConn
|
||||
pattern CON = Agent.CON PQEncOn
|
||||
pattern CON = A.CON PQEncOn
|
||||
|
||||
pattern Msg :: MsgBody -> ACommand 'Agent e
|
||||
pattern Msg msgBody <- MSG MsgMeta {integrity = MsgOk, pqEncryption = PQEncOn} _ msgBody
|
||||
@@ -168,13 +168,14 @@ smpCfgV7 = (smpCfg agentCfg) {serverVRange = V.mkVersionRange batchCmdsSMPVersio
|
||||
ntfCfgV2 :: ProtocolClientConfig NTFVersion
|
||||
ntfCfgV2 = (smpCfg agentCfg) {serverVRange = V.mkVersionRange (VersionNTF 1) authBatchCmdsNTFVersion}
|
||||
|
||||
-- TODO PQ test next version with PQ
|
||||
agentCfgVPrev :: AgentConfig
|
||||
agentCfgVPrev =
|
||||
agentCfg
|
||||
{ sndAuthAlg = C.AuthAlg C.SEd25519,
|
||||
smpAgentVRange = prevRange $ smpAgentVRange agentCfg,
|
||||
smpAgentVRange = \_ -> prevRange $ smpAgentVRange agentCfg PQEncOff,
|
||||
smpClientVRange = prevRange $ smpClientVRange agentCfg,
|
||||
e2eEncryptVRange = prevRange $ e2eEncryptVRange agentCfg,
|
||||
e2eEncryptVRange = \_ -> prevRange $ e2eEncryptVRange agentCfg PQEncOff,
|
||||
smpCfg = smpCfgVPrev
|
||||
}
|
||||
|
||||
@@ -187,7 +188,7 @@ agentCfgV7 =
|
||||
}
|
||||
|
||||
agentCfgRatchetVPrev :: AgentConfig
|
||||
agentCfgRatchetVPrev = agentCfg {e2eEncryptVRange = prevRange $ e2eEncryptVRange agentCfg}
|
||||
agentCfgRatchetVPrev = agentCfg {e2eEncryptVRange = \_ -> prevRange $ e2eEncryptVRange agentCfg PQEncOff}
|
||||
|
||||
prevRange :: VersionRange v -> VersionRange v
|
||||
prevRange vr = vr {maxVersion = max (minVersion vr) (prevVersion $ maxVersion vr)}
|
||||
@@ -223,14 +224,14 @@ inAnyOrder g rs = do
|
||||
expected r rp = rp r
|
||||
|
||||
createConnection :: AgentErrorMonad m => AgentClient -> UserId -> Bool -> SConnectionMode c -> Maybe CRClientData -> SubscriptionMode -> m (ConnId, ConnectionRequestUri c)
|
||||
createConnection c userId enableNtfs cMode clientData = Agent.createConnection c userId enableNtfs cMode clientData (IKNoPQ PQEncOn)
|
||||
createConnection c userId enableNtfs cMode clientData = A.createConnection c userId enableNtfs cMode clientData (IKNoPQ PQEncOn)
|
||||
|
||||
joinConnection :: AgentErrorMonad m => AgentClient -> UserId -> Bool -> ConnectionRequestUri c -> ConnInfo -> SubscriptionMode -> m ConnId
|
||||
joinConnection c userId enableNtfs cReq connInfo = Agent.joinConnection c userId enableNtfs cReq connInfo PQEncOn
|
||||
joinConnection c userId enableNtfs cReq connInfo = A.joinConnection c userId enableNtfs cReq connInfo PQEncOn
|
||||
|
||||
sendMessage :: AgentErrorMonad m => AgentClient -> ConnId -> SMP.MsgFlags -> MsgBody -> m AgentMsgId
|
||||
sendMessage c connId msgFlags msgBody = do
|
||||
(msgId, pqEnc) <- Agent.sendMessage c connId PQEncOn msgFlags msgBody
|
||||
(msgId, pqEnc) <- A.sendMessage c connId PQEncOn msgFlags msgBody
|
||||
liftIO $ pqEnc `shouldBe` PQEncOn
|
||||
pure msgId
|
||||
|
||||
@@ -267,6 +268,7 @@ functionalAPITests t = do
|
||||
testIncreaseConnAgentVersionMaxCompatible t
|
||||
it "should increase when connection was negotiated on different versions" $
|
||||
testIncreaseConnAgentVersionStartDifferentVersion t
|
||||
-- TODO PQ tests for upgrading connection to PQ encryption
|
||||
it "should deliver message after client restart" $
|
||||
testDeliverClientRestart t
|
||||
it "should deliver messages to the user once, even if repeat delivery is made by the server (no ACK)" $
|
||||
@@ -424,29 +426,25 @@ canCreateQueue allowNew (srvAuth, srvVersion) (clntAuth, clntVersion) =
|
||||
let v = basicAuthSMPVersion
|
||||
in allowNew && (isNothing srvAuth || (srvVersion >= v && clntVersion >= v && srvAuth == clntAuth))
|
||||
|
||||
testMatrix2 :: ATransport -> (AgentClient -> AgentClient -> AgentMsgId -> IO ()) -> Spec
|
||||
-- TODO PQ test next version with PQ
|
||||
testMatrix2 :: ATransport -> (PQEncryption -> AgentClient -> AgentClient -> AgentMsgId -> IO ()) -> Spec
|
||||
testMatrix2 t runTest = do
|
||||
it "v7" $ withSmpServerV7 t $ runTestCfg2 agentCfgV7 agentCfgV7 3 runTest
|
||||
it "v7 to current" $ withSmpServerV7 t $ runTestCfg2 agentCfgV7 agentCfg 3 runTest
|
||||
it "current to v7" $ withSmpServerV7 t $ runTestCfg2 agentCfg agentCfgV7 3 runTest
|
||||
it "current with v7 server" $ withSmpServerV7 t $ runTestCfg2 agentCfg agentCfg 3 runTest
|
||||
it "current" $ withSmpServer t $ runTestCfg2 agentCfg agentCfg 3 runTest
|
||||
skip "TODO PQ versioning" $ describe "TODO fails with previous version" $ do
|
||||
it "prev" $ withSmpServer t $ runTestCfg2 agentCfgVPrev agentCfgVPrev 3 runTest
|
||||
it "prev to current" $ withSmpServer t $ runTestCfg2 agentCfgVPrev agentCfg 3 runTest
|
||||
it "current to prev" $ withSmpServer t $ runTestCfg2 agentCfg agentCfgVPrev 3 runTest
|
||||
it "v7" $ withSmpServerV7 t $ runTestCfg2 agentCfgV7 agentCfgV7 3 $ runTest PQEncOn
|
||||
it "v7 to current" $ withSmpServerV7 t $ runTestCfg2 agentCfgV7 agentCfg 3 $ runTest PQEncOn
|
||||
it "current to v7" $ withSmpServerV7 t $ runTestCfg2 agentCfg agentCfgV7 3 $ runTest PQEncOn
|
||||
it "current with v7 server" $ withSmpServerV7 t $ runTestCfg2 agentCfg agentCfg 3 $ runTest PQEncOn
|
||||
it "current" $ withSmpServer t $ runTestCfg2 agentCfg agentCfg 3 $ runTest PQEncOn
|
||||
it "prev" $ withSmpServer t $ runTestCfg2 agentCfgVPrev agentCfgVPrev 3 $ runTest PQEncOff
|
||||
it "prev to current" $ withSmpServer t $ runTestCfg2 agentCfgVPrev agentCfg 3 $ runTest PQEncOff
|
||||
it "current to prev" $ withSmpServer t $ runTestCfg2 agentCfg agentCfgVPrev 3 $ runTest PQEncOff
|
||||
|
||||
testRatchetMatrix2 :: ATransport -> (AgentClient -> AgentClient -> AgentMsgId -> IO ()) -> Spec
|
||||
-- TODO PQ test next version with PQ
|
||||
testRatchetMatrix2 :: ATransport -> (PQEncryption -> AgentClient -> AgentClient -> AgentMsgId -> IO ()) -> Spec
|
||||
testRatchetMatrix2 t runTest = do
|
||||
it "ratchet current" $ withSmpServer t $ runTestCfg2 agentCfg agentCfg 3 runTest
|
||||
skip "TODO PQ versioning" $ describe "TODO fails with previous version" $ do
|
||||
pendingV "ratchet prev" $ withSmpServer t $ runTestCfg2 agentCfgRatchetVPrev agentCfgRatchetVPrev 3 runTest
|
||||
pendingV "ratchets prev to current" $ withSmpServer t $ runTestCfg2 agentCfgRatchetVPrev agentCfg 3 runTest
|
||||
pendingV "ratchets current to prev" $ withSmpServer t $ runTestCfg2 agentCfg agentCfgRatchetVPrev 3 runTest
|
||||
where
|
||||
pendingV d =
|
||||
let vr = e2eEncryptVRange agentCfg
|
||||
in if minVersion vr == maxVersion vr then skip "previous version is not supported" . it d else it d
|
||||
it "ratchet current" $ withSmpServer t $ runTestCfg2 agentCfg agentCfg 3 $ runTest PQEncOn
|
||||
it "ratchet prev" $ withSmpServer t $ runTestCfg2 agentCfgRatchetVPrev agentCfgRatchetVPrev 3 $ runTest PQEncOff
|
||||
it "ratchets prev to current" $ withSmpServer t $ runTestCfg2 agentCfgRatchetVPrev agentCfg 3 $ runTest PQEncOff
|
||||
it "ratchets current to prev" $ withSmpServer t $ runTestCfg2 agentCfg agentCfgRatchetVPrev 3 $ runTest PQEncOff
|
||||
|
||||
testServerMatrix2 :: ATransport -> (InitialAgentServers -> IO ()) -> Spec
|
||||
testServerMatrix2 t runTest = do
|
||||
@@ -468,40 +466,40 @@ withAgentClientsCfg2 aCfg bCfg runTest = do
|
||||
withAgentClients2 :: (AgentClient -> AgentClient -> IO ()) -> IO ()
|
||||
withAgentClients2 = withAgentClientsCfg2 agentCfg agentCfg
|
||||
|
||||
runAgentClientTest :: HasCallStack => AgentClient -> AgentClient -> AgentMsgId -> IO ()
|
||||
runAgentClientTest alice@AgentClient {} bob baseId =
|
||||
runAgentClientTest :: HasCallStack => PQEncryption -> AgentClient -> AgentClient -> AgentMsgId -> IO ()
|
||||
runAgentClientTest pqEnc alice@AgentClient {} bob baseId =
|
||||
runRight_ $ do
|
||||
(bobId, qInfo) <- createConnection alice 1 True SCMInvitation Nothing SMSubscribe
|
||||
aliceId <- joinConnection bob 1 True qInfo "bob's connInfo" SMSubscribe
|
||||
(bobId, qInfo) <- A.createConnection alice 1 True SCMInvitation Nothing (IKNoPQ pqEnc) SMSubscribe
|
||||
aliceId <- A.joinConnection bob 1 True qInfo "bob's connInfo" pqEnc SMSubscribe
|
||||
("", _, CONF confId _ "bob's connInfo") <- get alice
|
||||
allowConnection alice bobId confId "alice's connInfo"
|
||||
get alice ##> ("", bobId, CON)
|
||||
get alice ##> ("", bobId, A.CON pqEnc)
|
||||
get bob ##> ("", aliceId, INFO "alice's connInfo")
|
||||
get bob ##> ("", aliceId, CON)
|
||||
get bob ##> ("", aliceId, A.CON pqEnc)
|
||||
-- message IDs 1 to 3 (or 1 to 4 in v1) get assigned to control messages, so first MSG is assigned ID 4
|
||||
1 <- msgId <$> sendMessage alice bobId SMP.noMsgFlags "hello"
|
||||
1 <- msgId <$> A.sendMessage alice bobId pqEnc SMP.noMsgFlags "hello"
|
||||
get alice ##> ("", bobId, SENT $ baseId + 1)
|
||||
2 <- msgId <$> sendMessage alice bobId SMP.noMsgFlags "how are you?"
|
||||
2 <- msgId <$> A.sendMessage alice bobId pqEnc SMP.noMsgFlags "how are you?"
|
||||
get alice ##> ("", bobId, SENT $ baseId + 2)
|
||||
get bob =##> \case ("", c, Msg "hello") -> c == aliceId; _ -> False
|
||||
get bob =##> \case ("", c, Msg' _ pq "hello") -> c == aliceId && pq == pqEnc; _ -> False
|
||||
ackMessage bob aliceId (baseId + 1) Nothing
|
||||
get bob =##> \case ("", c, Msg "how are you?") -> c == aliceId; _ -> False
|
||||
get bob =##> \case ("", c, Msg' _ pq "how are you?") -> c == aliceId && pq == pqEnc; _ -> False
|
||||
ackMessage bob aliceId (baseId + 2) Nothing
|
||||
3 <- msgId <$> sendMessage bob aliceId SMP.noMsgFlags "hello too"
|
||||
3 <- msgId <$> A.sendMessage bob aliceId pqEnc SMP.noMsgFlags "hello too"
|
||||
get bob ##> ("", aliceId, SENT $ baseId + 3)
|
||||
4 <- msgId <$> sendMessage bob aliceId SMP.noMsgFlags "message 1"
|
||||
4 <- msgId <$> A.sendMessage bob aliceId pqEnc SMP.noMsgFlags "message 1"
|
||||
get bob ##> ("", aliceId, SENT $ baseId + 4)
|
||||
get alice =##> \case ("", c, Msg "hello too") -> c == bobId; _ -> False
|
||||
get alice =##> \case ("", c, Msg' _ pq "hello too") -> c == bobId && pq == pqEnc; _ -> False
|
||||
ackMessage alice bobId (baseId + 3) Nothing
|
||||
get alice =##> \case ("", c, Msg "message 1") -> c == bobId; _ -> False
|
||||
get alice =##> \case ("", c, Msg' _ pq "message 1") -> c == bobId && pq == pqEnc; _ -> False
|
||||
ackMessage alice bobId (baseId + 4) Nothing
|
||||
suspendConnection alice bobId
|
||||
5 <- msgId <$> sendMessage bob aliceId SMP.noMsgFlags "message 2"
|
||||
5 <- msgId <$> A.sendMessage bob aliceId pqEnc SMP.noMsgFlags "message 2"
|
||||
get bob ##> ("", aliceId, MERR (baseId + 5) (SMP AUTH))
|
||||
deleteConnection alice bobId
|
||||
liftIO $ noMessages alice "nothing else should be delivered to alice"
|
||||
where
|
||||
msgId = subtract baseId
|
||||
msgId = subtract baseId . fst
|
||||
|
||||
testAgentClient3 :: HasCallStack => IO ()
|
||||
testAgentClient3 = do
|
||||
@@ -529,42 +527,42 @@ testAgentClient3 = do
|
||||
get c =##> \case ("", connId, Msg "c5") -> connId == aIdForC; _ -> False
|
||||
ackMessage c aIdForC 5 Nothing
|
||||
|
||||
runAgentClientContactTest :: HasCallStack => AgentClient -> AgentClient -> AgentMsgId -> IO ()
|
||||
runAgentClientContactTest alice bob baseId =
|
||||
runAgentClientContactTest :: HasCallStack => PQEncryption -> AgentClient -> AgentClient -> AgentMsgId -> IO ()
|
||||
runAgentClientContactTest pqEnc alice bob baseId =
|
||||
runRight_ $ do
|
||||
(_, qInfo) <- createConnection alice 1 True SCMContact Nothing SMSubscribe
|
||||
aliceId <- joinConnection bob 1 True qInfo "bob's connInfo" SMSubscribe
|
||||
(_, qInfo) <- A.createConnection alice 1 True SCMContact Nothing (IKNoPQ pqEnc) SMSubscribe
|
||||
aliceId <- A.joinConnection bob 1 True qInfo "bob's connInfo" pqEnc SMSubscribe
|
||||
("", _, REQ invId _ "bob's connInfo") <- get alice
|
||||
bobId <- acceptContact alice True invId "alice's connInfo" PQEncOn SMSubscribe
|
||||
("", _, CONF confId _ "alice's connInfo") <- get bob
|
||||
allowConnection bob aliceId confId "bob's connInfo"
|
||||
get alice ##> ("", bobId, INFO "bob's connInfo")
|
||||
get alice ##> ("", bobId, CON)
|
||||
get bob ##> ("", aliceId, CON)
|
||||
get alice ##> ("", bobId, A.CON pqEnc)
|
||||
get bob ##> ("", aliceId, A.CON pqEnc)
|
||||
-- message IDs 1 to 3 (or 1 to 4 in v1) get assigned to control messages, so first MSG is assigned ID 4
|
||||
1 <- msgId <$> sendMessage alice bobId SMP.noMsgFlags "hello"
|
||||
1 <- msgId <$> A.sendMessage alice bobId pqEnc SMP.noMsgFlags "hello"
|
||||
get alice ##> ("", bobId, SENT $ baseId + 1)
|
||||
2 <- msgId <$> sendMessage alice bobId SMP.noMsgFlags "how are you?"
|
||||
2 <- msgId <$> A.sendMessage alice bobId pqEnc SMP.noMsgFlags "how are you?"
|
||||
get alice ##> ("", bobId, SENT $ baseId + 2)
|
||||
get bob =##> \case ("", c, Msg "hello") -> c == aliceId; _ -> False
|
||||
get bob =##> \case ("", c, Msg' _ pq "hello") -> c == aliceId && pq == pqEnc; _ -> False
|
||||
ackMessage bob aliceId (baseId + 1) Nothing
|
||||
get bob =##> \case ("", c, Msg "how are you?") -> c == aliceId; _ -> False
|
||||
get bob =##> \case ("", c, Msg' _ pq "how are you?") -> c == aliceId && pq == pqEnc; _ -> False
|
||||
ackMessage bob aliceId (baseId + 2) Nothing
|
||||
3 <- msgId <$> sendMessage bob aliceId SMP.noMsgFlags "hello too"
|
||||
3 <- msgId <$> A.sendMessage bob aliceId pqEnc SMP.noMsgFlags "hello too"
|
||||
get bob ##> ("", aliceId, SENT $ baseId + 3)
|
||||
4 <- msgId <$> sendMessage bob aliceId SMP.noMsgFlags "message 1"
|
||||
4 <- msgId <$> A.sendMessage bob aliceId pqEnc SMP.noMsgFlags "message 1"
|
||||
get bob ##> ("", aliceId, SENT $ baseId + 4)
|
||||
get alice =##> \case ("", c, Msg "hello too") -> c == bobId; _ -> False
|
||||
get alice =##> \case ("", c, Msg' _ pq "hello too") -> c == bobId && pq == pqEnc; _ -> False
|
||||
ackMessage alice bobId (baseId + 3) Nothing
|
||||
get alice =##> \case ("", c, Msg "message 1") -> c == bobId; _ -> False
|
||||
get alice =##> \case ("", c, Msg' _ pq "message 1") -> c == bobId && pq == pqEnc; _ -> False
|
||||
ackMessage alice bobId (baseId + 4) Nothing
|
||||
suspendConnection alice bobId
|
||||
5 <- msgId <$> sendMessage bob aliceId SMP.noMsgFlags "message 2"
|
||||
5 <- msgId <$> A.sendMessage bob aliceId pqEnc SMP.noMsgFlags "message 2"
|
||||
get bob ##> ("", aliceId, MERR (baseId + 5) (SMP AUTH))
|
||||
deleteConnection alice bobId
|
||||
liftIO $ noMessages alice "nothing else should be delivered to alice"
|
||||
where
|
||||
msgId = subtract baseId
|
||||
msgId = subtract baseId . fst
|
||||
|
||||
noMessages :: HasCallStack => AgentClient -> String -> Expectation
|
||||
noMessages c err = tryGet `shouldReturn` ()
|
||||
@@ -688,12 +686,12 @@ testAllowConnectionClientRestart t = do
|
||||
|
||||
testIncreaseConnAgentVersion :: HasCallStack => ATransport -> IO ()
|
||||
testIncreaseConnAgentVersion t = 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 1 2} initAgentServers testDB
|
||||
bob <- getSMPAgentClient' 2 agentCfg {smpAgentVRange = \_ -> mkVersionRange 1 2} initAgentServers testDB2
|
||||
withSmpServerStoreMsgLogOn t testPort $ \_ -> do
|
||||
(aliceId, bobId) <- runRight $ do
|
||||
(aliceId, bobId) <- makeConnection alice bob
|
||||
exchangeGreetingsMsgId 4 alice bobId bob aliceId
|
||||
(aliceId, bobId) <- makeConnection_ PQEncOff alice bob
|
||||
exchangeGreetingsMsgId_ PQEncOff 4 alice bobId bob aliceId
|
||||
checkVersion alice bobId 2
|
||||
checkVersion bob aliceId 2
|
||||
pure (aliceId, bobId)
|
||||
@@ -701,42 +699,42 @@ testIncreaseConnAgentVersion t = do
|
||||
-- version doesn't increase if incompatible
|
||||
|
||||
disconnectAgentClient alice
|
||||
alice2 <- getSMPAgentClient' 3 agentCfg {smpAgentVRange = mkVersionRange 1 3} initAgentServers testDB
|
||||
alice2 <- getSMPAgentClient' 3 agentCfg {smpAgentVRange = \_ -> mkVersionRange 1 3} initAgentServers testDB
|
||||
|
||||
runRight_ $ do
|
||||
subscribeConnection alice2 bobId
|
||||
exchangeGreetingsMsgId 6 alice2 bobId bob aliceId
|
||||
exchangeGreetingsMsgId_ PQEncOff 6 alice2 bobId bob aliceId
|
||||
checkVersion alice2 bobId 2
|
||||
checkVersion bob aliceId 2
|
||||
|
||||
-- version increases if compatible
|
||||
|
||||
disconnectAgentClient bob
|
||||
bob2 <- getSMPAgentClient' 4 agentCfg {smpAgentVRange = mkVersionRange 1 3} initAgentServers testDB2
|
||||
bob2 <- getSMPAgentClient' 4 agentCfg {smpAgentVRange = \_ -> mkVersionRange 1 3} initAgentServers testDB2
|
||||
|
||||
runRight_ $ do
|
||||
subscribeConnection bob2 aliceId
|
||||
exchangeGreetingsMsgId 8 alice2 bobId bob2 aliceId
|
||||
exchangeGreetingsMsgId_ PQEncOff 8 alice2 bobId bob2 aliceId
|
||||
checkVersion alice2 bobId 3
|
||||
checkVersion bob2 aliceId 3
|
||||
|
||||
-- version doesn't decrease, even if incompatible
|
||||
|
||||
disconnectAgentClient alice2
|
||||
alice3 <- getSMPAgentClient' 5 agentCfg {smpAgentVRange = mkVersionRange 2 2} initAgentServers testDB
|
||||
alice3 <- getSMPAgentClient' 5 agentCfg {smpAgentVRange = \_ -> mkVersionRange 2 2} initAgentServers testDB
|
||||
|
||||
runRight_ $ do
|
||||
subscribeConnection alice3 bobId
|
||||
exchangeGreetingsMsgId 10 alice3 bobId bob2 aliceId
|
||||
exchangeGreetingsMsgId_ PQEncOff 10 alice3 bobId bob2 aliceId
|
||||
checkVersion alice3 bobId 3
|
||||
checkVersion bob2 aliceId 3
|
||||
|
||||
disconnectAgentClient bob2
|
||||
bob3 <- getSMPAgentClient' 6 agentCfg {smpAgentVRange = mkVersionRange 1 1} initAgentServers testDB2
|
||||
bob3 <- getSMPAgentClient' 6 agentCfg {smpAgentVRange = \_ -> mkVersionRange 1 1} initAgentServers testDB2
|
||||
|
||||
runRight_ $ do
|
||||
subscribeConnection bob3 aliceId
|
||||
exchangeGreetingsMsgId 12 alice3 bobId bob3 aliceId
|
||||
exchangeGreetingsMsgId_ PQEncOff 12 alice3 bobId bob3 aliceId
|
||||
checkVersion alice3 bobId 3
|
||||
checkVersion bob3 aliceId 3
|
||||
disconnectAgentClient alice3
|
||||
@@ -749,12 +747,12 @@ checkVersion c connId v = do
|
||||
|
||||
testIncreaseConnAgentVersionMaxCompatible :: HasCallStack => ATransport -> IO ()
|
||||
testIncreaseConnAgentVersionMaxCompatible t = 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 1 2} initAgentServers testDB
|
||||
bob <- getSMPAgentClient' 2 agentCfg {smpAgentVRange = \_ -> mkVersionRange 1 2} initAgentServers testDB2
|
||||
withSmpServerStoreMsgLogOn t testPort $ \_ -> do
|
||||
(aliceId, bobId) <- runRight $ do
|
||||
(aliceId, bobId) <- makeConnection alice bob
|
||||
exchangeGreetingsMsgId 4 alice bobId bob aliceId
|
||||
(aliceId, bobId) <- makeConnection_ PQEncOff alice bob
|
||||
exchangeGreetingsMsgId_ PQEncOff 4 alice bobId bob aliceId
|
||||
checkVersion alice bobId 2
|
||||
checkVersion bob aliceId 2
|
||||
pure (aliceId, bobId)
|
||||
@@ -762,14 +760,14 @@ testIncreaseConnAgentVersionMaxCompatible t = do
|
||||
-- version increases to max compatible
|
||||
|
||||
disconnectAgentClient alice
|
||||
alice2 <- getSMPAgentClient' 3 agentCfg {smpAgentVRange = mkVersionRange 1 3} initAgentServers testDB
|
||||
alice2 <- getSMPAgentClient' 3 agentCfg {smpAgentVRange = \_ -> mkVersionRange 1 3} initAgentServers testDB
|
||||
disconnectAgentClient bob
|
||||
bob2 <- getSMPAgentClient' 4 agentCfg {smpAgentVRange = mkVersionRange 1 4} initAgentServers testDB2
|
||||
bob2 <- getSMPAgentClient' 4 agentCfg {smpAgentVRange = supportedSMPAgentVRange} initAgentServers testDB2
|
||||
|
||||
runRight_ $ do
|
||||
subscribeConnection alice2 bobId
|
||||
subscribeConnection bob2 aliceId
|
||||
exchangeGreetingsMsgId 6 alice2 bobId bob2 aliceId
|
||||
exchangeGreetingsMsgId_ PQEncOff 6 alice2 bobId bob2 aliceId
|
||||
checkVersion alice2 bobId 3
|
||||
checkVersion bob2 aliceId 3
|
||||
disconnectAgentClient alice2
|
||||
@@ -777,12 +775,12 @@ testIncreaseConnAgentVersionMaxCompatible t = do
|
||||
|
||||
testIncreaseConnAgentVersionStartDifferentVersion :: HasCallStack => ATransport -> IO ()
|
||||
testIncreaseConnAgentVersionStartDifferentVersion t = 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 1 2} initAgentServers testDB
|
||||
bob <- getSMPAgentClient' 2 agentCfg {smpAgentVRange = \_ -> mkVersionRange 1 3} initAgentServers testDB2
|
||||
withSmpServerStoreMsgLogOn t testPort $ \_ -> do
|
||||
(aliceId, bobId) <- runRight $ do
|
||||
(aliceId, bobId) <- makeConnection alice bob
|
||||
exchangeGreetingsMsgId 4 alice bobId bob aliceId
|
||||
(aliceId, bobId) <- makeConnection_ PQEncOff alice bob
|
||||
exchangeGreetingsMsgId_ PQEncOff 4 alice bobId bob aliceId
|
||||
checkVersion alice bobId 2
|
||||
checkVersion bob aliceId 2
|
||||
pure (aliceId, bobId)
|
||||
@@ -790,11 +788,11 @@ testIncreaseConnAgentVersionStartDifferentVersion t = do
|
||||
-- version increases to max compatible
|
||||
|
||||
disconnectAgentClient alice
|
||||
alice2 <- getSMPAgentClient' 3 agentCfg {smpAgentVRange = mkVersionRange 1 3} initAgentServers testDB
|
||||
alice2 <- getSMPAgentClient' 3 agentCfg {smpAgentVRange = \_ -> mkVersionRange 1 3} initAgentServers testDB
|
||||
|
||||
runRight_ $ do
|
||||
subscribeConnection alice2 bobId
|
||||
exchangeGreetingsMsgId 6 alice2 bobId bob aliceId
|
||||
exchangeGreetingsMsgId_ PQEncOff 6 alice2 bobId bob aliceId
|
||||
checkVersion alice2 bobId 3
|
||||
checkVersion bob aliceId 3
|
||||
disconnectAgentClient alice2
|
||||
@@ -1075,13 +1073,13 @@ setupDesynchronizedRatchet alice bob = do
|
||||
runRight_ $ do
|
||||
subscribeConnection bob2 aliceId
|
||||
|
||||
Left Agent.CMD {cmdErr = PROHIBITED} <- runExceptT $ synchronizeRatchet bob2 aliceId PQEncOn False
|
||||
Left A.CMD {cmdErr = PROHIBITED} <- runExceptT $ synchronizeRatchet bob2 aliceId PQEncOn False
|
||||
|
||||
8 <- sendMessage alice bobId SMP.noMsgFlags "hello 5"
|
||||
get alice ##> ("", bobId, SENT 8)
|
||||
get bob2 =##> ratchetSyncP aliceId RSRequired
|
||||
|
||||
Left Agent.CMD {cmdErr = PROHIBITED} <- runExceptT $ sendMessage bob2 aliceId SMP.noMsgFlags "hello 6"
|
||||
Left A.CMD {cmdErr = PROHIBITED} <- runExceptT $ sendMessage bob2 aliceId SMP.noMsgFlags "hello 6"
|
||||
pure ()
|
||||
|
||||
pure (aliceId, bobId, bob2)
|
||||
@@ -1265,13 +1263,13 @@ makeConnectionForUsers = makeConnectionForUsers_ PQEncOn
|
||||
|
||||
makeConnectionForUsers_ :: PQEncryption -> AgentClient -> UserId -> AgentClient -> UserId -> ExceptT AgentErrorType IO (ConnId, ConnId)
|
||||
makeConnectionForUsers_ pqEnc alice aliceUserId bob bobUserId = do
|
||||
(bobId, qInfo) <- Agent.createConnection alice aliceUserId True SCMInvitation Nothing (CR.IKNoPQ pqEnc) SMSubscribe
|
||||
aliceId <- Agent.joinConnection bob bobUserId True qInfo "bob's connInfo" pqEnc SMSubscribe
|
||||
(bobId, qInfo) <- A.createConnection alice aliceUserId True SCMInvitation Nothing (CR.IKNoPQ pqEnc) SMSubscribe
|
||||
aliceId <- A.joinConnection bob bobUserId True qInfo "bob's connInfo" pqEnc SMSubscribe
|
||||
("", _, CONF confId _ "bob's connInfo") <- get alice
|
||||
allowConnection alice bobId confId "alice's connInfo"
|
||||
get alice ##> ("", bobId, Agent.CON pqEnc)
|
||||
get alice ##> ("", bobId, A.CON pqEnc)
|
||||
get bob ##> ("", aliceId, INFO "alice's connInfo")
|
||||
get bob ##> ("", aliceId, Agent.CON pqEnc)
|
||||
get bob ##> ("", aliceId, A.CON pqEnc)
|
||||
pure (aliceId, bobId)
|
||||
|
||||
testInactiveNoSubs :: ATransport -> IO ()
|
||||
@@ -2032,7 +2030,7 @@ testAbortSwitchStarted servers = do
|
||||
liftIO $ rcvSwchStatuses' stats `shouldMatchList` [Just RSSwitchStarted]
|
||||
phaseRcv a bId SPStarted [Just RSSendingQADD, Nothing]
|
||||
-- repeat switch is prohibited
|
||||
Left Agent.CMD {cmdErr = PROHIBITED} <- runExceptT $ switchConnectionAsync a "" bId
|
||||
Left A.CMD {cmdErr = PROHIBITED} <- runExceptT $ switchConnectionAsync a "" bId
|
||||
-- abort current switch
|
||||
stats' <- abortConnectionSwitch a bId
|
||||
liftIO $ rcvSwchStatuses' stats' `shouldMatchList` [Nothing]
|
||||
@@ -2154,7 +2152,7 @@ testCannotAbortSwitchSecured servers = do
|
||||
withA' $ \a -> do
|
||||
phaseRcv a bId SPConfirmed [Just RSSendingQADD, Nothing]
|
||||
phaseRcv a bId SPSecured [Just RSSendingQUSE, Nothing]
|
||||
Left Agent.CMD {cmdErr = PROHIBITED} <- runExceptT $ abortConnectionSwitch a bId
|
||||
Left A.CMD {cmdErr = PROHIBITED} <- runExceptT $ abortConnectionSwitch a bId
|
||||
pure ()
|
||||
withA $ \a -> withB $ \b -> runRight_ $ do
|
||||
subscribeConnection a bId
|
||||
@@ -2346,53 +2344,61 @@ testDeliveryReceipts =
|
||||
get a =##> \case ("", c, Msg "hello too") -> c == bId; _ -> False
|
||||
ackMessage a bId 6 $ Just ""
|
||||
get b =##> \case ("", c, Rcvd 6) -> c == aId; _ -> False
|
||||
ackMessage b aId 7 (Just "") `catchError` \e -> liftIO $ e `shouldBe` Agent.CMD PROHIBITED
|
||||
ackMessage b aId 7 (Just "") `catchError` \e -> liftIO $ e `shouldBe` A.CMD PROHIBITED
|
||||
ackMessage b aId 7 Nothing
|
||||
|
||||
testDeliveryReceiptsVersion :: HasCallStack => ATransport -> IO ()
|
||||
testDeliveryReceiptsVersion t = do
|
||||
a <- getSMPAgentClient' 1 agentCfg {smpAgentVRange = mkVersionRange 1 3} initAgentServers testDB
|
||||
b <- getSMPAgentClient' 2 agentCfg {smpAgentVRange = mkVersionRange 1 3} initAgentServers testDB2
|
||||
a <- getSMPAgentClient' 1 agentCfg {smpAgentVRange = \_ -> mkVersionRange 1 3} initAgentServers testDB
|
||||
b <- getSMPAgentClient' 2 agentCfg {smpAgentVRange = \_ -> mkVersionRange 1 3} initAgentServers testDB2
|
||||
withSmpServerStoreMsgLogOn t testPort $ \_ -> do
|
||||
(aId, bId) <- runRight $ do
|
||||
(aId, bId) <- makeConnection a b
|
||||
(aId, bId) <- makeConnection_ PQEncOff a b
|
||||
checkVersion a bId 3
|
||||
checkVersion b aId 3
|
||||
4 <- sendMessage a bId SMP.noMsgFlags "hello"
|
||||
(4, _) <- A.sendMessage a bId PQEncOff SMP.noMsgFlags "hello"
|
||||
get a ##> ("", bId, SENT 4)
|
||||
get b =##> \case ("", c, Msg "hello") -> c == aId; _ -> False
|
||||
get b =##> \case ("", c, Msg' 4 PQEncOff "hello") -> c == aId; _ -> False
|
||||
ackMessage b aId 4 $ Just ""
|
||||
liftIO $ noMessages a "no delivery receipt (unsupported version)"
|
||||
5 <- sendMessage b aId SMP.noMsgFlags "hello too"
|
||||
(5, _) <- A.sendMessage b aId PQEncOff SMP.noMsgFlags "hello too"
|
||||
get b ##> ("", aId, SENT 5)
|
||||
get a =##> \case ("", c, Msg "hello too") -> c == bId; _ -> False
|
||||
get a =##> \case ("", c, Msg' 5 PQEncOff "hello too") -> c == bId; _ -> False
|
||||
ackMessage a bId 5 $ Just ""
|
||||
liftIO $ noMessages b "no delivery receipt (unsupported version)"
|
||||
pure (aId, bId)
|
||||
|
||||
disconnectAgentClient a
|
||||
disconnectAgentClient b
|
||||
a' <- getSMPAgentClient' 3 agentCfg {smpAgentVRange = mkVersionRange 1 4} initAgentServers testDB
|
||||
b' <- getSMPAgentClient' 4 agentCfg {smpAgentVRange = mkVersionRange 1 4} initAgentServers testDB2
|
||||
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 6 a' bId b' aId
|
||||
exchangeGreetingsMsgId_ PQEncOff 6 a' bId b' aId
|
||||
checkVersion a' bId 4
|
||||
checkVersion b' aId 4
|
||||
8 <- sendMessage a' bId SMP.noMsgFlags "hello"
|
||||
(8, PQEncOff) <- A.sendMessage a' bId PQEncOn SMP.noMsgFlags "hello"
|
||||
get a' ##> ("", bId, SENT 8)
|
||||
get b' =##> \case ("", c, Msg "hello") -> c == aId; _ -> False
|
||||
get b' =##> \case ("", c, Msg' 8 PQEncOff "hello") -> c == aId; _ -> False
|
||||
ackMessage b' aId 8 $ Just ""
|
||||
get a' =##> \case ("", c, Rcvd 8) -> c == bId; _ -> False
|
||||
ackMessage a' bId 9 Nothing
|
||||
10 <- sendMessage b' aId SMP.noMsgFlags "hello too"
|
||||
(10, PQEncOff) <- A.sendMessage b' aId PQEncOn SMP.noMsgFlags "hello too"
|
||||
get b' ##> ("", aId, SENT 10)
|
||||
get a' =##> \case ("", c, Msg "hello too") -> c == bId; _ -> False
|
||||
get a' =##> \case ("", c, Msg' 10 PQEncOff "hello too") -> c == bId; _ -> False
|
||||
ackMessage a' bId 10 $ Just ""
|
||||
get b' =##> \case ("", c, Rcvd 10) -> c == aId; _ -> False
|
||||
ackMessage b' aId 11 Nothing
|
||||
-- TODO PQ this part hangs when waiting for Rcvd, because connection tries to upgrade to PQ encryption.
|
||||
-- replacing 2 PQEncOn with PQEncOff above prevents hanging.
|
||||
-- (12, _) <- A.sendMessage a' bId PQEncOn SMP.noMsgFlags "hello 2"
|
||||
-- get a' ##> ("", bId, SENT 12)
|
||||
-- get b' =##> \case ("", c, Msg' 12 PQEncOff "hello 2") -> c == aId; _ -> False
|
||||
-- ackMessage b' aId 12 $ Just ""
|
||||
-- get a' =##> \case ("", c, Rcvd 12) -> c == bId; _ -> False
|
||||
-- ackMessage a' bId 13 Nothing
|
||||
disconnectAgentClient a'
|
||||
disconnectAgentClient b'
|
||||
|
||||
@@ -2562,12 +2568,12 @@ exchangeGreetingsMsgId = exchangeGreetingsMsgId_ PQEncOn
|
||||
|
||||
exchangeGreetingsMsgId_ :: HasCallStack => PQEncryption -> Int64 -> AgentClient -> ConnId -> AgentClient -> ConnId -> ExceptT AgentErrorType IO ()
|
||||
exchangeGreetingsMsgId_ pqEnc msgId alice bobId bob aliceId = do
|
||||
msgId1 <- Agent.sendMessage alice bobId pqEnc SMP.noMsgFlags "hello"
|
||||
msgId1 <- A.sendMessage alice bobId pqEnc SMP.noMsgFlags "hello"
|
||||
liftIO $ msgId1 `shouldBe` (msgId, pqEnc)
|
||||
get alice ##> ("", bobId, SENT msgId)
|
||||
get bob =##> \case ("", c, Msg' mId pq "hello") -> c == aliceId && mId == msgId && pq == pqEnc; _ -> False
|
||||
ackMessage bob aliceId msgId Nothing
|
||||
msgId2 <- Agent.sendMessage bob aliceId pqEnc SMP.noMsgFlags "hello too"
|
||||
msgId2 <- A.sendMessage bob aliceId pqEnc SMP.noMsgFlags "hello too"
|
||||
let msgId' = msgId + 1
|
||||
liftIO $ msgId2 `shouldBe` (msgId', pqEnc)
|
||||
get bob ##> ("", aliceId, SENT msgId')
|
||||
|
||||
Reference in New Issue
Block a user