mirror of
https://github.com/simplex-chat/simplexmq.git
synced 2026-10-12 02:22:45 +00:00
agent: prepare invitation link, create ratchet when connection is prepared (#1880)
* agent: prepare invitation link * simplify * refactor * fix * change min SMP version to 15 * remove dead code for SMP version <= 15 * update * diff * create ratchet when connection is prepared * createRatchetIfNotExists * clean up, only check client notices for contacts, fix orphan record * simplify * refactor --------- Co-authored-by: Evgeny @ SimpleX Chat <[email protected]>
This commit is contained in:
co-authored by
Evgeny @ SimpleX Chat
parent
c18e0297c4
commit
a2f985dc68
+120
-55
@@ -63,7 +63,10 @@ module Simplex.Messaging.Agent
|
||||
createConnection,
|
||||
prepareConnectionLink,
|
||||
createConnectionForLink,
|
||||
prepareInvitationLink,
|
||||
createInvitationForLink,
|
||||
setConnShortLink,
|
||||
prepareConnShortLink,
|
||||
deleteConnShortLink,
|
||||
getConnShortLink,
|
||||
resolveSimplexName,
|
||||
@@ -439,22 +442,39 @@ createConnection c nm userId enableNtfs checkNotices = withAgentEnv c .::: newCo
|
||||
-- Caller provides root signing key pair and link entity ID.
|
||||
-- Returns the created link and internal params.
|
||||
-- The link address is fully determined at this point.
|
||||
prepareConnectionLink :: AgentClient -> UserId -> C.KeyPairEd25519 -> ByteString -> Bool -> Maybe CRClientData -> CR.InitialKeys -> UseRatchetKeys -> Maybe SMPServerWithAuth -> AE (CreatedConnLink 'CMContact, PreparedLinkParams)
|
||||
prepareConnectionLink :: AgentClient -> UserId -> C.KeyPairEd25519 -> ByteString -> Bool -> Maybe CRClientData -> CR.InitialKeys -> UseRatchetKeys -> Maybe SMPServerWithAuth -> AE (CreatedConnLink 'CMContact, PreparedLinkParams (Maybe (RatchetKeyId, CR.RcvE2EPrivRatchetParams 'C.X448)))
|
||||
prepareConnectionLink c userId rootKey linkEntityId checkNotices clientData pqInitKeys useDR srv_ =
|
||||
withAgentEnv c $ prepareConnectionLink' c userId rootKey linkEntityId checkNotices clientData pqInitKeys useDR srv_
|
||||
{-# INLINE prepareConnectionLink #-}
|
||||
|
||||
-- | Create connection for prepared link (single network call).
|
||||
-- Validates that server response matches the prepared link.
|
||||
createConnectionForLink :: AgentClient -> NetworkRequestMode -> UserId -> Bool -> CreatedConnLink 'CMContact -> PreparedLinkParams -> UserConnLinkData 'CMContact -> SubscriptionMode -> AE ConnId
|
||||
createConnectionForLink :: AgentClient -> NetworkRequestMode -> UserId -> Bool -> CreatedConnLink 'CMContact -> PreparedLinkParams (Maybe (RatchetKeyId, CR.RcvE2EPrivRatchetParams 'C.X448)) -> UserConnLinkData 'CMContact -> SubscriptionMode -> AE ConnId
|
||||
createConnectionForLink c nm userId enableNtfs = withAgentEnv c .:: createConnectionForLink' c nm userId enableNtfs
|
||||
{-# INLINE createConnectionForLink #-}
|
||||
|
||||
-- | Prepare one-time invitation link (no network call).
|
||||
-- Returns the invitation and internal params with the link key.
|
||||
-- The link ID is assigned by the server when the connection is created.
|
||||
prepareInvitationLink :: AgentClient -> UserId -> CR.InitialKeys -> Maybe SMPServerWithAuth -> AE (ConnectionRequestUri 'CMInvitation, PreparedLinkParams (CR.RcvE2EPrivRatchetParams 'C.X448))
|
||||
prepareInvitationLink c userId pqInitKeys srv_ = withAgentEnv c $ prepareInvitationLink' c userId pqInitKeys srv_
|
||||
{-# INLINE prepareInvitationLink #-}
|
||||
|
||||
-- | Create connection for prepared invitation link (single network call).
|
||||
-- Returns the created link with the link ID assigned by the server.
|
||||
createInvitationForLink :: AgentClient -> NetworkRequestMode -> UserId -> Bool -> ConnectionRequestUri 'CMInvitation -> PreparedLinkParams (CR.RcvE2EPrivRatchetParams 'C.X448) -> UserConnLinkData 'CMInvitation -> SubscriptionMode -> AE (ConnId, CreatedConnLink 'CMInvitation)
|
||||
createInvitationForLink c nm userId enableNtfs = withAgentEnv c .:: createInvitationForLink' c nm userId enableNtfs
|
||||
{-# INLINE createInvitationForLink #-}
|
||||
|
||||
-- | Create or update user's contact connection short link
|
||||
setConnShortLink :: AgentClient -> NetworkRequestMode -> ConnId -> SConnectionMode c -> UserConnLinkData c -> Maybe CRClientData -> NewRatchetKeys -> Maybe CR.InitialKeys -> AE (ConnShortLink c)
|
||||
setConnShortLink c = withAgentEnv c .:::. setConnShortLink' c
|
||||
{-# INLINE setConnShortLink #-}
|
||||
|
||||
prepareConnShortLink :: AgentClient -> ConnId -> Maybe CRClientData -> AE (ConnShortLink 'CMContact)
|
||||
prepareConnShortLink c = withAgentEnv c .: prepareConnShortLink' c
|
||||
{-# INLINE prepareConnShortLink #-}
|
||||
|
||||
deleteConnShortLink :: AgentClient -> NetworkRequestMode -> ConnId -> SConnectionMode c -> AE ()
|
||||
deleteConnShortLink c = withAgentEnv c .:. deleteConnShortLink' c
|
||||
{-# INLINE deleteConnShortLink #-}
|
||||
@@ -1035,30 +1055,20 @@ newConn c nm userId enableNtfs checkNotices cMode linkData_ clientData pqInitKey
|
||||
|
||||
-- | Prepare connection link for contact mode (no network, no database).
|
||||
-- Caller provides root signing key pair and link entity ID.
|
||||
prepareConnectionLink' :: AgentClient -> UserId -> C.KeyPairEd25519 -> ByteString -> Bool -> Maybe CRClientData -> CR.InitialKeys -> UseRatchetKeys -> Maybe SMPServerWithAuth -> AM (CreatedConnLink 'CMContact, PreparedLinkParams)
|
||||
prepareConnectionLink' c userId rootKey@(_, plpRootPrivKey) linkEntityId checkNotices clientData pqInitKeys useDR srv_ = do
|
||||
g <- asks random
|
||||
plpSrvWithAuth@(ProtoServerWithAuth srv _) <- maybe (getSMPServer c userId) pure srv_
|
||||
when checkNotices $ checkClientNotices c plpSrvWithAuth
|
||||
AgentConfig {smpClientVRange, smpAgentVRange} <- asks config
|
||||
plpNonce@(C.CbNonce corrId) <- atomically $ C.randomCbNonce g
|
||||
plpQueueE2EKeys@(e2ePubKey, _) <- atomically $ C.generateKeyPair g
|
||||
prepareConnectionLink' :: AgentClient -> UserId -> C.KeyPairEd25519 -> ByteString -> Bool -> Maybe CRClientData -> CR.InitialKeys -> UseRatchetKeys -> Maybe SMPServerWithAuth -> AM (CreatedConnLink 'CMContact, PreparedLinkParams (Maybe (RatchetKeyId, CR.RcvE2EPrivRatchetParams 'C.X448)))
|
||||
prepareConnectionLink' c userId rootKey linkEntityId checkNotices clientData pqInitKeys useDR srv_ = do
|
||||
srvWithAuth@(ProtoServerWithAuth srv _) <- maybe (getSMPServer c userId) pure srv_
|
||||
when checkNotices $ checkClientNotices c srvWithAuth
|
||||
addrKeys_ <- if useDR then Just <$> generateAddressRatchetKeys pqInitKeys else pure Nothing
|
||||
let sndId = SMP.EntityId $ B.take 24 $ C.sha3_384 corrId
|
||||
qUri = SMPQueueUri smpClientVRange $ SMPQueueAddress srv sndId e2ePubKey (Just QMContact)
|
||||
connReq = CRContactUri (ConnReqUriData SSSimplex smpAgentVRange [qUri] clientData) (fst <$> addrKeys_)
|
||||
(plpLinkKey, plpSignedFixedData) = SL.encodeSignFixedData rootKey smpAgentVRange connReq (Just linkEntityId)
|
||||
ccLink = CCLink connReq $ Just $ CSLContact SLSServer CCTContact srv plpLinkKey
|
||||
params = PreparedLinkParams {plpNonce, plpQueueE2EKeys, plpLinkKey, plpRootPrivKey, plpSignedFixedData, plpSrvWithAuth, plpInitKeys = pqInitKeys, plpAddressKeys = snd <$> addrKeys_}
|
||||
pure (ccLink, params)
|
||||
(connReq, params@PreparedLinkParams {plpLinkKey}) <-
|
||||
prepareLinkParams rootKey (Just linkEntityId) clientData pqInitKeys srvWithAuth QMContact (`CRContactUri` (fst <$> addrKeys_)) (snd <$> addrKeys_)
|
||||
pure (CCLink connReq $ Just $ CSLContact SLSServer CCTContact srv plpLinkKey, params)
|
||||
|
||||
-- | Create connection for prepared link (single network call).
|
||||
createConnectionForLink' :: AgentClient -> NetworkRequestMode -> UserId -> Bool -> CreatedConnLink 'CMContact -> PreparedLinkParams -> UserConnLinkData 'CMContact -> SubscriptionMode -> AM ConnId
|
||||
createConnectionForLink' c nm userId enableNtfs (CCLink connReq _) PreparedLinkParams {plpNonce, plpQueueE2EKeys, plpLinkKey, plpRootPrivKey, plpSignedFixedData, plpSrvWithAuth, plpInitKeys, plpAddressKeys} userLinkData subMode = do
|
||||
createConnectionForLink' :: AgentClient -> NetworkRequestMode -> UserId -> Bool -> CreatedConnLink 'CMContact -> PreparedLinkParams (Maybe (RatchetKeyId, CR.RcvE2EPrivRatchetParams 'C.X448)) -> UserConnLinkData 'CMContact -> SubscriptionMode -> AM ConnId
|
||||
createConnectionForLink' c nm userId enableNtfs (CCLink connReq _) params@PreparedLinkParams {plpLinkKey, plpRootPrivKey, plpSignedFixedData, plpRatchetKeys} userLinkData subMode = do
|
||||
g <- asks random
|
||||
AgentConfig {smpAgentVRange} <- asks config
|
||||
connId <- newConnNoQueues c userId enableNtfs SCMContact (CR.connPQEncryption plpInitKeys)
|
||||
mapM_ (storeAddressRatchetKeys c connId) plpAddressKeys
|
||||
let CRContactUri ConnReqUriData {crSmpQueues = SMPQueueUri _ SMPQueueAddress {senderId = sndId} :| _} addrKeys_ = connReq
|
||||
userLinkData' = case addrKeys_ of
|
||||
Just arKeys -> let UserContactLinkData ucd = userLinkData in UserContactLinkData ucd {ratchetKeys = Just arKeys}
|
||||
@@ -1066,13 +1076,54 @@ createConnectionForLink' c nm userId enableNtfs (CCLink connReq _) PreparedLinkP
|
||||
md = SL.encodeSignUserData SCMContact plpRootPrivKey smpAgentVRange userLinkData'
|
||||
linkData = (plpSignedFixedData, md)
|
||||
qd <- encryptContactLinkData g plpRootPrivKey plpLinkKey sndId linkData
|
||||
(_, qUri) <-
|
||||
createRcvQueue c nm userId connId plpSrvWithAuth enableNtfs subMode (Just plpNonce) qd plpQueueE2EKeys
|
||||
`catchE` \e -> withStore' c (`deleteConnRecord` connId) >> throwE e
|
||||
(connId, _, qUri) <- createLinkQueue c nm userId enableNtfs SCMContact params subMode qd $ \connId -> mapM_ (storeAddressRatchetKeys c connId) plpRatchetKeys
|
||||
let SMPQueueUri _ SMPQueueAddress {senderId = actualSndId} = qUri
|
||||
unless (actualSndId == sndId) $ throwE $ INTERNAL "createConnectionForLink: sender ID mismatch"
|
||||
pure connId
|
||||
|
||||
-- | Prepare one-time invitation link (no network, no database).
|
||||
prepareInvitationLink' :: AgentClient -> UserId -> CR.InitialKeys -> Maybe SMPServerWithAuth -> AM (ConnectionRequestUri 'CMInvitation, PreparedLinkParams (CR.RcvE2EPrivRatchetParams 'C.X448))
|
||||
prepareInvitationLink' c userId pqInitKeys srv_ = do
|
||||
g <- asks random
|
||||
srvWithAuth <- maybe (getSMPServer c userId) pure srv_
|
||||
e2eVR <- asks $ e2eEncryptVRange . config
|
||||
rootKey <- atomically $ C.generateKeyPair g
|
||||
(pks, e2eRcvParams) <- liftIO $ CR.generateRcvE2EParams g (maxVersion e2eVR) (CR.initialPQEncryption True pqInitKeys)
|
||||
prepareLinkParams rootKey Nothing Nothing pqInitKeys srvWithAuth QMMessaging (`CRInvitationUri` toVersionRangeT e2eRcvParams e2eVR) pks
|
||||
|
||||
-- | Create connection for prepared invitation link (single network call).
|
||||
createInvitationForLink' :: AgentClient -> NetworkRequestMode -> UserId -> Bool -> ConnectionRequestUri 'CMInvitation -> PreparedLinkParams (CR.RcvE2EPrivRatchetParams 'C.X448) -> UserConnLinkData 'CMInvitation -> SubscriptionMode -> AM (ConnId, CreatedConnLink 'CMInvitation)
|
||||
createInvitationForLink' c nm userId enableNtfs connReq params@PreparedLinkParams {plpLinkKey, plpRootPrivKey, plpSignedFixedData, plpInitKeys, plpRatchetKeys} userLinkData subMode = do
|
||||
g <- asks random
|
||||
AgentConfig {smpAgentVRange} <- asks config
|
||||
let CRInvitationUri ConnReqUriData {crSmpQueues = qUri@(SMPQueueUri _ SMPQueueAddress {senderId = sndId}) :| _} _ = connReq
|
||||
md = SL.encodeSignUserData SCMInvitation plpRootPrivKey smpAgentVRange userLinkData
|
||||
srvData <- liftError id $ SL.encryptLinkData g (SL.invShortLinkKdf plpLinkKey) (plpSignedFixedData, md)
|
||||
let qd = CQRMessaging $ Just CQRData {linkKey = plpLinkKey, privSigKey = plpRootPrivKey, srvReq = (sndId, srvData)}
|
||||
(connId, rq, qUri') <- createLinkQueue c nm userId enableNtfs SCMInvitation params subMode qd $ \connId -> withStore' c $ \db -> createRatchetX3dhKeys db connId plpRatchetKeys
|
||||
(connId,) <$> connReqWithShortLink plpInitKeys qUri connReq qUri' rq
|
||||
|
||||
-- | Generate queue keys and sign fixed link data (no network, no database).
|
||||
prepareLinkParams :: ConnectionModeI c => C.KeyPairEd25519 -> Maybe ByteString -> Maybe CRClientData -> CR.InitialKeys -> SMPServerWithAuth -> QueueMode -> (ConnReqUriData -> ConnectionRequestUri c) -> k -> AM (ConnectionRequestUri c, PreparedLinkParams k)
|
||||
prepareLinkParams rootKey@(_, plpRootPrivKey) linkEntityId clientData plpInitKeys plpSrvWithAuth@(ProtoServerWithAuth srv _) qm mkConnReq plpRatchetKeys = do
|
||||
g <- asks random
|
||||
AgentConfig {smpClientVRange, smpAgentVRange} <- asks config
|
||||
plpNonce@(C.CbNonce corrId) <- atomically $ C.randomCbNonce g
|
||||
plpQueueE2EKeys@(e2ePubKey, _) <- atomically $ C.generateKeyPair g
|
||||
let sndId = SMP.EntityId $ B.take 24 $ C.sha3_384 corrId
|
||||
qUri = SMPQueueUri smpClientVRange $ SMPQueueAddress srv sndId e2ePubKey (Just qm)
|
||||
connReq = mkConnReq $ ConnReqUriData SSSimplex smpAgentVRange [qUri] clientData
|
||||
(plpLinkKey, plpSignedFixedData) = SL.encodeSignFixedData rootKey smpAgentVRange connReq linkEntityId
|
||||
pure (connReq, PreparedLinkParams {plpNonce, plpQueueE2EKeys, plpLinkKey, plpRootPrivKey, plpSignedFixedData, plpSrvWithAuth, plpInitKeys, plpRatchetKeys})
|
||||
|
||||
-- | Create connection and its queue with link data; the connection is deleted when either step fails.
|
||||
createLinkQueue :: AgentClient -> NetworkRequestMode -> UserId -> Bool -> SConnectionMode c -> PreparedLinkParams k -> SubscriptionMode -> ClntQueueReqData -> (ConnId -> AM ()) -> AM (ConnId, RcvQueue, SMPQueueUri)
|
||||
createLinkQueue c nm userId enableNtfs cMode PreparedLinkParams {plpNonce, plpQueueE2EKeys, plpSrvWithAuth, plpInitKeys} subMode qd storeKeys = do
|
||||
connId <- newConnNoQueues c userId enableNtfs cMode (CR.connPQEncryption plpInitKeys)
|
||||
uncurry (connId,,)
|
||||
<$> (storeKeys connId >> createRcvQueue c nm userId connId plpSrvWithAuth enableNtfs subMode (Just plpNonce) qd plpQueueE2EKeys)
|
||||
`catchE` \e -> withStore' c (`deleteConnRecord` connId) >> throwE e
|
||||
|
||||
generateAddressRatchetKeys :: CR.InitialKeys -> AM (AddressRatchetKeys, (RatchetKeyId, CR.RcvE2EPrivRatchetParams 'C.X448))
|
||||
generateAddressRatchetKeys pqInitKeys = do
|
||||
g <- asks random
|
||||
@@ -1197,7 +1248,7 @@ setConnShortLink' c nm connId cMode userLinkData clientData rotateKeys pqKeys_ =
|
||||
prepareContactLinkData ConnData {} rq@RcvQueue {shortLink} (UserContactLinkData ucd) = do
|
||||
liftEitherWith (CMD PROHIBITED . ("setConnShortLink: " <>)) $ validateOwners shortLink ucd
|
||||
g <- asks random
|
||||
AgentConfig {smpClientVRange = vr, smpAgentVRange} <- asks config
|
||||
AgentConfig {smpAgentVRange} <- asks config
|
||||
-- rotate makes fresh keys from InitialKeys; otherwise keep current keys or create based on InitialKeys if there are no ratchet keys
|
||||
let currKeys = currentAddressRatchetKeys c connId
|
||||
ratchetKeys <- case pqKeys_ of
|
||||
@@ -1207,32 +1258,47 @@ setConnShortLink' c nm connId cMode userLinkData clientData rotateKeys pqKeys_ =
|
||||
Nothing -> currKeys
|
||||
let ud = UserContactLinkData ucd {ratchetKeys}
|
||||
cslContact = CSLContact SLSServer CCTContact (qServer rq)
|
||||
case shortLink of
|
||||
Just ShortLinkCreds {shortLinkId, shortLinkKey, linkPrivSigKey, linkEncFixedData} -> do
|
||||
(ShortLinkCreds {shortLinkId, shortLinkKey, linkPrivSigKey, linkEncFixedData}, k) <- case shortLink of
|
||||
Just creds@ShortLinkCreds {shortLinkId, shortLinkKey} -> do
|
||||
let (linkId, k) = SL.contactShortLinkKdf shortLinkKey
|
||||
unless (shortLinkId == linkId) $ throwE $ INTERNAL "setConnShortLink: link ID is not derived from link"
|
||||
d <- liftError id $ SL.encryptUserData g k $ SL.encodeSignUserData SCMContact linkPrivSigKey smpAgentVRange ud
|
||||
pure (rq, linkId, cslContact shortLinkKey, (linkEncFixedData, d))
|
||||
Nothing -> do
|
||||
sigKeys@(_, privSigKey) <- atomically $ C.generateKeyPair @'C.Ed25519 g
|
||||
let qUri = SMPQueueUri vr $ (rcvSMPQueueAddress rq) {queueMode = Just QMContact}
|
||||
connReq = CRContactUri (ConnReqUriData SSSimplex smpAgentVRange [qUri] clientData) ratchetKeys
|
||||
(linkKey, linkData) = SL.encodeSignLinkData sigKeys smpAgentVRange connReq Nothing ud
|
||||
(linkId, k) = SL.contactShortLinkKdf linkKey
|
||||
srvData <- liftError id $ SL.encryptLinkData g k linkData
|
||||
let slCreds = ShortLinkCreds linkId linkKey privSigKey Nothing (fst srvData)
|
||||
withStore' c $ \db -> updateShortLinkCreds db rq slCreds
|
||||
pure (rq, linkId, cslContact linkKey, srvData)
|
||||
prepareInvLinkData :: RcvQueue -> UserConnLinkData 'CMInvitation -> AM (RcvQueue, SMP.LinkId, ConnShortLink 'CMInvitation, QueueLinkData)
|
||||
prepareInvLinkData rq@RcvQueue {shortLink} ud = case shortLink of
|
||||
Just ShortLinkCreds {shortLinkId, shortLinkKey, linkPrivSigKey, linkEncFixedData} -> do
|
||||
g <- asks random
|
||||
AgentConfig {smpAgentVRange} <- asks config
|
||||
let k = SL.invShortLinkKdf shortLinkKey
|
||||
d <- liftError id $ SL.encryptUserData g k $ SL.encodeSignUserData SCMInvitation linkPrivSigKey smpAgentVRange ud
|
||||
let sl = CSLInvitation SLSServer (qServer rq) shortLinkId shortLinkKey
|
||||
pure (rq, shortLinkId, sl, (linkEncFixedData, d))
|
||||
Nothing -> throwE $ CMD PROHIBITED "setConnShortLink: no ShortLinkCreds in invitation"
|
||||
pure (creds, k)
|
||||
Nothing -> newContactLinkCreds c rq clientData
|
||||
d <- liftError id $ SL.encryptUserData g k $ SL.encodeSignUserData SCMContact linkPrivSigKey smpAgentVRange ud
|
||||
pure (rq, shortLinkId, cslContact shortLinkKey, (linkEncFixedData, d))
|
||||
|
||||
newContactLinkCreds :: AgentClient -> RcvQueue -> Maybe CRClientData -> AM (ShortLinkCreds, C.SbKey)
|
||||
newContactLinkCreds c rq clientData = do
|
||||
g <- asks random
|
||||
AgentConfig {smpClientVRange = vr, smpAgentVRange} <- asks config
|
||||
sigKeys@(_, privSigKey) <- atomically $ C.generateKeyPair @'C.Ed25519 g
|
||||
let qUri = SMPQueueUri vr $ (rcvSMPQueueAddress rq) {queueMode = Just QMContact}
|
||||
connReq = CRContactUri (ConnReqUriData SSSimplex smpAgentVRange [qUri] clientData) Nothing
|
||||
(linkKey, fixedData) = SL.encodeSignFixedData sigKeys smpAgentVRange connReq Nothing
|
||||
(linkId, k) = SL.contactShortLinkKdf linkKey
|
||||
encFixedData <- liftError id $ SL.encryptFixedData g k fixedData
|
||||
let slCreds = ShortLinkCreds linkId linkKey privSigKey Nothing encFixedData
|
||||
withStore' c $ \db -> updateShortLinkCreds db rq slCreds
|
||||
pure (slCreds, k)
|
||||
|
||||
prepareInvLinkData :: RcvQueue -> UserConnLinkData 'CMInvitation -> AM (RcvQueue, SMP.LinkId, ConnShortLink 'CMInvitation, QueueLinkData)
|
||||
prepareInvLinkData rq@RcvQueue {shortLink} ud = case shortLink of
|
||||
Just ShortLinkCreds {shortLinkId, shortLinkKey, linkPrivSigKey, linkEncFixedData} -> do
|
||||
g <- asks random
|
||||
AgentConfig {smpAgentVRange} <- asks config
|
||||
let k = SL.invShortLinkKdf shortLinkKey
|
||||
d <- liftError id $ SL.encryptUserData g k $ SL.encodeSignUserData SCMInvitation linkPrivSigKey smpAgentVRange ud
|
||||
let sl = CSLInvitation SLSServer (qServer rq) shortLinkId shortLinkKey
|
||||
pure (rq, shortLinkId, sl, (linkEncFixedData, d))
|
||||
Nothing -> throwE $ CMD PROHIBITED "setConnShortLink: no ShortLinkCreds in invitation"
|
||||
|
||||
prepareConnShortLink' :: AgentClient -> ConnId -> Maybe CRClientData -> AM (ConnShortLink 'CMContact)
|
||||
prepareConnShortLink' c connId clientData =
|
||||
withConnLock c connId "prepareConnShortLink" $
|
||||
withStore c (`getConn` connId) >>= \case
|
||||
SomeConn _ (ContactConnection _ rq@RcvQueue {shortLink}) ->
|
||||
CSLContact SLSServer CCTContact (qServer rq) . shortLinkKey <$> maybe (fst <$> newContactLinkCreds c rq clientData) pure shortLink
|
||||
_ -> throwE $ CMD PROHIBITED "prepareConnShortLink: not contact address"
|
||||
|
||||
deleteConnShortLink' :: AgentClient -> NetworkRequestMode -> ConnId -> SConnectionMode c -> AM ()
|
||||
deleteConnShortLink' c nm connId cMode =
|
||||
@@ -1443,11 +1509,11 @@ newConnToAccept c userId connId enableNtfs invId pqSup = do
|
||||
pure (connId', CRBRatchet $ ratchetVerifyCodes ratchetState)
|
||||
|
||||
newConnToAcceptDR :: AgentClient -> UserId -> ConnId -> DRInvitation -> Bool -> AM ConnData
|
||||
newConnToAcceptDR c userId connId DRInvitation {agentVersion, pqSupport} enableNtfs = do
|
||||
newConnToAcceptDR c userId connId DRInvitation {ratchetState, agentVersion, pqSupport} enableNtfs = do
|
||||
g <- asks random
|
||||
let cData = ConnData {userId, connId, connAgentVersion = agentVersion, enableNtfs, lastExternalSndId = 0, deleted = False, ratchetSyncState = RSOk, pqSupport, serviceRequestExpiresAt = Nothing}
|
||||
connId' <- withStore c $ \db -> createNewConn db g cData SCMInvitation
|
||||
pure (cData {connId = connId'} :: ConnData)
|
||||
withStore c $ \db ->
|
||||
createNewConn db g cData SCMInvitation $>>= \connId' -> Right (cData {connId = connId'} :: ConnData) <$ createRatchet db connId' ratchetState
|
||||
|
||||
joinConn :: AgentClient -> NetworkRequestMode -> UserId -> ConnId -> Bool -> ConnectionRequestUri c -> ConnInfo -> PQSupport -> SubscriptionMode -> AM SndQueueSecured
|
||||
joinConn c nm userId connId enableNtfs cReq cInfo pqSupport subMode = do
|
||||
@@ -1505,9 +1571,8 @@ startJoinInvitationDR c userId ConnData {connId} DRInvitation {ratchetState, rep
|
||||
qInfo <- maybe (throwE $ AGENT A_VERSION) pure $ replyQueue `proveCompatible` clientVRange
|
||||
(q, _) <- lift $ newSndQueue userId connId qInfo Nothing
|
||||
withStore c $ \db -> runExceptT $ do
|
||||
liftIO $ lockConnForUpdate db connId
|
||||
liftIO $ createRatchet db connId ratchetState
|
||||
ExceptT $ updateNewConnSnd db connId q
|
||||
sq <- ExceptT $ updateNewConnSnd db connId q
|
||||
sq <$ liftIO (createRatchet db connId ratchetState)
|
||||
|
||||
connRequestAgentVersion :: AgentClient -> ConnectionRequestUri c -> IO (Maybe VersionSMPA)
|
||||
connRequestAgentVersion c cReq = withAgentEnv' c $ case cReq of
|
||||
|
||||
@@ -246,7 +246,6 @@ import Simplex.Messaging.Crypto.Ratchet
|
||||
RcvE2ERatchetParams,
|
||||
RcvE2ERatchetParamsUri,
|
||||
SndE2ERatchetParams,
|
||||
RcvE2EPrivRatchetParams,
|
||||
pattern PQSupportOff,
|
||||
pattern PQSupportOn,
|
||||
)
|
||||
@@ -1627,7 +1626,7 @@ newtype LinkKey = LinkKey ByteString -- sha3-256(fixed_data)
|
||||
instance ToField LinkKey where toField (LinkKey s) = toField $ Binary s
|
||||
|
||||
-- | Parameters for creating a connection with a prepared link.
|
||||
data PreparedLinkParams = PreparedLinkParams
|
||||
data PreparedLinkParams k = PreparedLinkParams
|
||||
{ -- | Correlation ID / determines sender ID
|
||||
plpNonce :: C.CbNonce,
|
||||
-- | Queue E2EE DH key pair
|
||||
@@ -1642,8 +1641,8 @@ data PreparedLinkParams = PreparedLinkParams
|
||||
plpSrvWithAuth :: SMPServerWithAuth,
|
||||
-- | Initial PQ keys
|
||||
plpInitKeys :: InitialKeys,
|
||||
-- | Contact address double ratchet keys
|
||||
plpAddressKeys :: Maybe (RatchetKeyId, RcvE2EPrivRatchetParams 'C.X448)
|
||||
-- | Contact address double ratchet keys or invitation X3DH keys
|
||||
plpRatchetKeys :: k
|
||||
}
|
||||
|
||||
instance ConnectionModeI c => ToField (ConnectionLink c) where toField = toField . Binary . strEncode
|
||||
|
||||
@@ -17,6 +17,7 @@ module Simplex.Messaging.Crypto.ShortLink
|
||||
encodeSignUserData,
|
||||
newOwnerAuth,
|
||||
encryptLinkData,
|
||||
encryptFixedData,
|
||||
encryptUserData,
|
||||
decryptLinkData,
|
||||
)
|
||||
@@ -84,9 +85,10 @@ newOwnerAuth g ownerId signingKey = do
|
||||
pure (ownerPrivKey, OwnerAuth {ownerId, ownerKey, authOwnerSig})
|
||||
|
||||
encryptLinkData :: TVar ChaChaDRG -> C.SbKey -> (ByteString, ByteString) -> ExceptT AgentErrorType IO QueueLinkData
|
||||
encryptLinkData g k = bimapM (encrypt fixedDataPaddedLength) (encrypt userDataPaddedLength)
|
||||
where
|
||||
encrypt len = encryptData g k len
|
||||
encryptLinkData g k = bimapM (encryptFixedData g k) (encryptUserData g k)
|
||||
|
||||
encryptFixedData :: TVar ChaChaDRG -> C.SbKey -> ByteString -> ExceptT AgentErrorType IO EncDataBytes
|
||||
encryptFixedData g k s = encryptData g k fixedDataPaddedLength s
|
||||
|
||||
encryptUserData :: TVar ChaChaDRG -> C.SbKey -> ByteString -> ExceptT AgentErrorType IO EncDataBytes
|
||||
encryptUserData g k s = encryptData g k userDataPaddedLength s
|
||||
|
||||
@@ -92,7 +92,7 @@ import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..), Env (..), InitialAg
|
||||
import Simplex.Messaging.Agent.Protocol hiding (CON, CONF, INFO, REQ, SENT)
|
||||
import qualified Simplex.Messaging.Agent.Protocol as A
|
||||
import Simplex.Messaging.Agent.Store (Connection' (..), SomeConn' (..), StoredRcvQueue (..))
|
||||
import Simplex.Messaging.Agent.Store.AgentStore (deleteRatchetKeyHashesExpired, getConn, getRatchetX3dhKeys)
|
||||
import Simplex.Messaging.Agent.Store.AgentStore (deleteRatchetKeyHashesExpired, getConn, getConnIds, getRatchetX3dhKeys)
|
||||
import Simplex.Messaging.Agent.Store.Common (DBStore (..), withTransaction)
|
||||
import Simplex.Messaging.Agent.Store.Interface
|
||||
import qualified Simplex.Messaging.Agent.Store.DB as DB
|
||||
@@ -371,6 +371,10 @@ functionalAPITests ps = do
|
||||
testAddDRViaSetConnShortLink ps
|
||||
it "should resume DR accept after a transient failure (reuse the send queue and ratchet)" $
|
||||
testAcceptContactDRResumeAfterOffline ps
|
||||
it "should store the ratchet when DR accept is prepared" $
|
||||
withSmpServer ps $ testAcceptContactDRPrepared False
|
||||
it "should create the ratchet at DR accept when the prepared connection has none" $
|
||||
withSmpServer ps $ testAcceptContactDRPrepared True
|
||||
it "should support rejecting contact request" $
|
||||
withSmpServer ps testRejectContactRequest
|
||||
it "should communicate rejection reason via double ratchet" $
|
||||
@@ -421,6 +425,10 @@ functionalAPITests ps = do
|
||||
it "should connect via added contact short link after restart" $ testAddContactShortLinkRestart ps
|
||||
it "should create and get short links with the old contact queues" $ testOldContactQueueShortLink ps
|
||||
it "should connect via prepared connection link" $ testPrepareCreateConnectionLink ps
|
||||
it "should connect via prepared invitation link" $ testPrepareCreateInvitationLink ps
|
||||
it "should not create connection for prepared invitation link with too large data" $ testPrepareInvitationLinkTooLarge ps
|
||||
it "should not create connection for prepared contact link with too large data" $ testPrepareContactLinkTooLarge ps
|
||||
it "should connect via short link prepared for existing contact connection" $ testPrepareConnShortLink ps
|
||||
describe "Message delivery" $ do
|
||||
describe "update connection agent version on received messages" $ do
|
||||
it "should increase if compatible, shouldn'ps decrease" $
|
||||
@@ -1241,6 +1249,30 @@ testAcceptContactDRResumeAfterOffline ps = withAgentClients2 $ \alice bob -> do
|
||||
("", _, A.CONF confId _ _ "alice's connInfo") <- get bob
|
||||
allowConfirmGreet alice bobId bob aliceId confId addrIK pqEnc
|
||||
|
||||
testAcceptContactDRPrepared :: HasCallStack => Bool -> IO ()
|
||||
testAcceptContactDRPrepared ratchetDeleted =
|
||||
withAgentClients2 $ \alice bob -> runRight_ $ do
|
||||
let userLinkData = UserContactLinkData UserContactData {direct = True, owners = [], relays = [], userData = UserLinkData "test user data", ratchetKeys = Nothing}
|
||||
pqEnc = PQEncryption $ pqConnectionMode IKPQOn PQSupportOn
|
||||
(_, CCLink connReq _) <- A.createConnection alice NRMInteractive 1 True True SCMContact (Just userLinkData) Nothing IKPQOn True SMSubscribe
|
||||
(aliceId, CRBRatchet codes) <- A.prepareConnectionToJoin bob 1 True connReq PQSupportOn
|
||||
void $ A.joinConnection bob NRMInteractive 1 aliceId True connReq "bob's connInfo" PQSupportOn SMSubscribe
|
||||
("", _, A.REQ invId _ _ "bob's connInfo" (CRBRatchet reqCodes) _) <- get alice
|
||||
(bobId, CRBRatchet acceptCodes) <- A.prepareConnectionToAccept alice 1 True invId PQSupportOn
|
||||
liftIO $ do
|
||||
reqCodes `shouldBe` codes
|
||||
acceptCodes `shouldBe` codes
|
||||
if ratchetDeleted
|
||||
then do
|
||||
liftIO $ withTransaction (store $ agentEnv alice) (`DB.execute_` "DELETE FROM ratchets")
|
||||
Left (CONN NOT_FOUND _) <- tryError $ getConnectionVerifyCodes alice bobId
|
||||
pure ()
|
||||
else getConnectionVerifyCodes alice bobId >>= liftIO . (`shouldBe` codes)
|
||||
void $ acceptContact alice 1 bobId True invId "alice's connInfo" PQSupportOn SMSubscribe
|
||||
("", _, A.CONF confId _ _ "alice's connInfo") <- get bob
|
||||
allowConfirmGreet alice bobId bob aliceId confId IKPQOn pqEnc
|
||||
getConnectionVerifyCodes alice bobId >>= liftIO . (`shouldBe` codes)
|
||||
|
||||
runAgentClientContactTestPQ3 :: HasCallStack => Bool -> (AgentClient, InitialKeys) -> (AgentClient, PQSupport) -> (AgentClient, PQSupport) -> AgentMsgId -> IO ()
|
||||
runAgentClientContactTestPQ3 viaProxy (alice, aPQ) (bob, bPQ) (tom, tPQ) baseId = runRight_ $ do
|
||||
(_, CCLink qInfo Nothing) <- A.createConnection alice NRMInteractive 1 True True SCMContact Nothing Nothing aPQ False SMSubscribe
|
||||
@@ -2055,6 +2087,64 @@ testPrepareCreateConnectionLink ps = withSmpServer ps $ withAgentClients2 $ \a b
|
||||
get b ##> ("", bId, CON)
|
||||
exchangeGreetings a aId b bId
|
||||
|
||||
testPrepareCreateInvitationLink :: HasCallStack => (ASrvTransport, AStoreType) -> IO ()
|
||||
testPrepareCreateInvitationLink ps = withSmpServer ps $ withAgentClients2 $ \a b -> do
|
||||
let userData = UserLinkData "test user data"
|
||||
runRight_ $ do
|
||||
(preparedReq, preparedParams@PreparedLinkParams {plpLinkKey}) <- A.prepareInvitationLink a 1 CR.IKPQOn Nothing
|
||||
(bId, CCLink connReq (Just shortLink@(CSLInvitation _ _ _ linkKey))) <- A.createInvitationForLink a NRMInteractive 1 True preparedReq preparedParams (UserInvLinkData userData) SMSubscribe
|
||||
liftIO $ linkKey `shouldBe` plpLinkKey
|
||||
(_, connData', connReq') <- getConnShortLink b 1 shortLink
|
||||
liftIO $ connReq' `shouldBe` preparedReq
|
||||
liftIO $ connReq `shouldNotBe` preparedReq
|
||||
liftIO $ linkUserData connData' `shouldBe` userData
|
||||
testJoinConn_ False True a bId b connReq
|
||||
|
||||
testPrepareInvitationLinkTooLarge :: HasCallStack => (ASrvTransport, AStoreType) -> IO ()
|
||||
testPrepareInvitationLinkTooLarge ps = withSmpServer ps $ withAgent 1 agentCfg initAgentServers testDB $ \a -> do
|
||||
runRight_ $ do
|
||||
(preparedReq, preparedParams) <- A.prepareInvitationLink a 1 CR.IKPQOn Nothing
|
||||
Left (A.CMD LARGE _) <- tryError $ A.createInvitationForLink a NRMInteractive 1 True preparedReq preparedParams (UserInvLinkData $ UserLinkData $ B.replicate 14000 'a') SMSubscribe
|
||||
pure ()
|
||||
withTransaction (store $ agentEnv a) getConnIds `shouldReturn` []
|
||||
|
||||
testPrepareContactLinkTooLarge :: HasCallStack => (ASrvTransport, AStoreType) -> IO ()
|
||||
testPrepareContactLinkTooLarge ps = withSmpServer ps $ withAgent 1 agentCfg initAgentServers testDB $ \a -> do
|
||||
let userCtData = UserContactData {direct = True, owners = [], relays = [], userData = UserLinkData $ B.replicate 14000 'a', ratchetKeys = Nothing}
|
||||
g <- C.newRandom
|
||||
rootKey <- atomically $ C.generateKeyPair g
|
||||
linkEntId <- atomically $ C.randomBytes 32 g
|
||||
runRight_ $ do
|
||||
(ccLink, preparedParams) <- A.prepareConnectionLink a 1 rootKey linkEntId True Nothing CR.IKPQOn True Nothing
|
||||
Left (A.CMD LARGE _) <- tryError $ A.createConnectionForLink a NRMInteractive 1 True ccLink preparedParams (UserContactLinkData userCtData) SMSubscribe
|
||||
pure ()
|
||||
withTransaction (store $ agentEnv a) getConnIds `shouldReturn` []
|
||||
|
||||
testPrepareConnShortLink :: HasCallStack => (ASrvTransport, AStoreType) -> IO ()
|
||||
testPrepareConnShortLink ps = withSmpServer ps $ withAgentClients2 $ \a b -> do
|
||||
let userCtData = UserContactData {direct = True, owners = [], relays = [], userData = UserLinkData "test user data", ratchetKeys = Nothing}
|
||||
runRight_ $ do
|
||||
(contactId, CCLink _ Nothing) <- A.createConnection a NRMInteractive 1 True True SCMContact Nothing Nothing CR.IKPQOn False SMSubscribe
|
||||
shortLink <- A.prepareConnShortLink a contactId Nothing
|
||||
shortLink' <- A.prepareConnShortLink a contactId Nothing
|
||||
liftIO $ shortLink' `shouldBe` shortLink
|
||||
shortLink'' <- A.setConnShortLink a NRMInteractive contactId SCMContact (UserContactLinkData userCtData) Nothing False Nothing
|
||||
liftIO $ shortLink'' `shouldBe` shortLink
|
||||
(_, ContactLinkData _ userCtData', connReq) <- getConnShortLink b 1 shortLink
|
||||
liftIO $ userCtData' `shouldBe` userCtData
|
||||
(aId, sndSecure) <- joinConnection b 1 True connReq "bob's connInfo" SMSubscribe
|
||||
liftIO $ sndSecure `shouldBe` False
|
||||
("", _, REQ invId _ "bob's connInfo") <- get a
|
||||
(bId, _) <- A.prepareConnectionToAccept a 1 True invId PQSupportOn
|
||||
sndSecure' <- acceptContact a 1 bId True invId "alice's connInfo" PQSupportOn SMSubscribe
|
||||
liftIO $ sndSecure' `shouldBe` True
|
||||
("", _, CONF confId _ "alice's connInfo") <- get b
|
||||
allowConnection b aId confId "bob's connInfo"
|
||||
get a ##> ("", bId, INFO "bob's connInfo")
|
||||
get a ##> ("", bId, CON)
|
||||
get b ##> ("", aId, CON)
|
||||
exchangeGreetings a bId b aId
|
||||
|
||||
connReqWithKeys :: BinaryConnectionRequestUri m -> Maybe AddressRatchetKeys -> ConnectionRequestUri m
|
||||
connReqWithKeys cr rk = case cr of
|
||||
BCRInvitationUri crData e2eParams -> CRInvitationUri crData e2eParams
|
||||
|
||||
Reference in New Issue
Block a user