Skip to content

Commit a94ca62

Browse files
agent: allow changing invitation link data before it is secured (#1552)
* agent: setInvitationShortLink api * Eq instance * allow changing link data on server, refactor * fix * encodings * remove link data after connection * Revert "encodings" This reverts commit f8e254c. --------- Co-authored-by: Evgeny Poberezkin <[email protected]>
1 parent 53b7246 commit a94ca62

File tree

5 files changed

+86
-52
lines changed

5 files changed

+86
-52
lines changed

src/Simplex/Messaging/Agent.hs

Lines changed: 43 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ module Simplex.Messaging.Agent
5656
deleteConnectionAsync,
5757
deleteConnectionsAsync,
5858
createConnection,
59-
setContactShortLink,
60-
deleteContactShortLink,
59+
setConnShortLink,
60+
deleteConnShortLink,
6161
getConnShortLink,
6262
deleteLocalInvShortLink,
6363
changeConnectionUser,
@@ -372,13 +372,13 @@ createConnection c userId enableNtfs = withAgentEnv c .::. newConn c userId enab
372372
{-# INLINE createConnection #-}
373373

374374
-- | Create or update user's contact connection short link
375-
setContactShortLink :: AgentClient -> ConnId -> ConnInfo -> Maybe CRClientData -> AE (ConnShortLink 'CMContact)
376-
setContactShortLink c = withAgentEnv c .:. setContactShortLink' c
377-
{-# INLINE setContactShortLink #-}
375+
setConnShortLink :: AgentClient -> ConnId -> SConnectionMode c -> ConnInfo -> Maybe CRClientData -> AE (ConnShortLink c)
376+
setConnShortLink c = withAgentEnv c .:: setConnShortLink' c
377+
{-# INLINE setConnShortLink #-}
378378

379-
deleteContactShortLink :: AgentClient -> ConnId -> AE ()
380-
deleteContactShortLink c = withAgentEnv c . deleteContactShortLink' c
381-
{-# INLINE deleteContactShortLink #-}
379+
deleteConnShortLink :: AgentClient -> ConnId -> SConnectionMode c -> AE ()
380+
deleteConnShortLink c = withAgentEnv c .: deleteConnShortLink' c
381+
{-# INLINE deleteConnShortLink #-}
382382

383383
-- | Get and verify data from short link. For 1-time invitations it preserves the key to allow retries
384384
getConnShortLink :: AgentClient -> UserId -> ConnShortLink c -> AE (ConnectionRequestUri c, ConnLinkData c)
@@ -833,26 +833,28 @@ newConn c userId enableNtfs cMode userData_ clientData pqInitKeys subMode = do
833833
(connId,) <$> newRcvConnSrv c userId connId enableNtfs cMode userData_ clientData pqInitKeys subMode srv
834834
`catchE` \e -> withStore' c (`deleteConnRecord` connId) >> throwE e
835835

836-
setContactShortLink' :: AgentClient -> ConnId -> ConnInfo -> Maybe CRClientData -> AM (ConnShortLink 'CMContact)
837-
setContactShortLink' c connId userData clientData =
838-
withConnLock c connId "setContactShortLink" $
839-
withStore c (`getConn` connId) >>= \case
840-
SomeConn _ (ContactConnection _ rq) -> do
841-
(lnkId, linkKey, d) <- prepareLinkData rq
842-
addQueueLink c rq lnkId d
843-
pure $ CSLContact SLSServer CCTContact (qServer rq) linkKey
844-
_ -> throwE $ CMD PROHIBITED "setContactShortLink: not contact address"
836+
setConnShortLink' :: AgentClient -> ConnId -> SConnectionMode c -> ConnInfo -> Maybe CRClientData -> AM (ConnShortLink c)
837+
setConnShortLink' c connId cMode userData clientData =
838+
withConnLock c connId "setConnShortLink" $ do
839+
SomeConn _ conn <- withStore c (`getConn` connId)
840+
(rq, lnkId, sl, d) <- case (conn, cMode) of
841+
(ContactConnection _ rq, SCMContact) -> prepareContactLinkData rq
842+
(RcvConnection _ rq, SCMInvitation) -> prepareInvLinkData rq
843+
_ -> throwE $ CMD PROHIBITED "setConnShortLink: invalid connection or mode"
844+
addQueueLink c rq lnkId d
845+
pure sl
845846
where
846-
prepareLinkData :: RcvQueue -> AM (SMP.LinkId, LinkKey, QueueLinkData)
847-
prepareLinkData rq@RcvQueue {server, sndId, e2ePrivKey, shortLink} = do
847+
prepareContactLinkData :: RcvQueue -> AM (RcvQueue, SMP.LinkId, ConnShortLink 'CMContact, QueueLinkData)
848+
prepareContactLinkData rq@RcvQueue {server, sndId, e2ePrivKey, shortLink} = do
848849
g <- asks random
849850
AgentConfig {smpClientVRange = vr, smpAgentVRange} <- asks config
851+
let cslContact = CSLContact SLSServer CCTContact (qServer rq)
850852
case shortLink of
851853
Just ShortLinkCreds {shortLinkId, shortLinkKey, linkPrivSigKey, linkEncFixedData} -> do
852854
let (linkId, k) = SL.contactShortLinkKdf shortLinkKey
853-
unless (shortLinkId == linkId) $ throwE $ INTERNAL "setContactShortLink: link ID is not derived from link"
855+
unless (shortLinkId == linkId) $ throwE $ INTERNAL "setConnShortLink: link ID is not derived from link"
854856
d <- liftError id $ SL.encryptUserData g k $ SL.encodeSignUserData linkPrivSigKey smpAgentVRange userData
855-
pure (linkId, shortLinkKey, (linkEncFixedData, d))
857+
pure (rq, linkId, cslContact shortLinkKey, (linkEncFixedData, d))
856858
Nothing -> do
857859
sigKeys@(_, privSigKey) <- atomically $ C.generateKeyPair @'C.Ed25519 g
858860
let qUri = SMPQueueUri vr $ SMPQueueAddress server sndId (C.publicKey e2ePrivKey) (Just QMContact)
@@ -862,14 +864,26 @@ setContactShortLink' c connId userData clientData =
862864
srvData <- liftError id $ SL.encryptLinkData g k linkData
863865
let slCreds = ShortLinkCreds linkId linkKey privSigKey (fst srvData)
864866
withStore' c $ \db -> updateShortLinkCreds db rq slCreds
865-
pure (linkId, linkKey, srvData)
866-
867-
deleteContactShortLink' :: AgentClient -> ConnId -> AM ()
868-
deleteContactShortLink' c connId =
869-
withConnLock c connId "deleteContactShortLink" $
870-
withStore c (`getConn` connId) >>= \case
871-
SomeConn _ (ContactConnection _ rq) -> deleteQueueLink c rq
872-
_ -> throwE $ CMD PROHIBITED "deleteContactShortLink: not contact address"
867+
pure (rq, linkId, cslContact linkKey, srvData)
868+
prepareInvLinkData :: RcvQueue -> AM (RcvQueue, SMP.LinkId, ConnShortLink 'CMInvitation, QueueLinkData)
869+
prepareInvLinkData rq@RcvQueue {shortLink} = case shortLink of
870+
Just ShortLinkCreds {shortLinkId, shortLinkKey, linkPrivSigKey, linkEncFixedData} -> do
871+
g <- asks random
872+
AgentConfig {smpAgentVRange} <- asks config
873+
let k = SL.invShortLinkKdf shortLinkKey
874+
d <- liftError id $ SL.encryptUserData g k $ SL.encodeSignUserData linkPrivSigKey smpAgentVRange userData
875+
let sl = CSLInvitation SLSServer (qServer rq) shortLinkId shortLinkKey
876+
pure (rq, shortLinkId, sl, (linkEncFixedData, d))
877+
Nothing -> throwE $ CMD PROHIBITED "setConnShortLink: no ShortLinkCreds in invitation"
878+
879+
deleteConnShortLink' :: AgentClient -> ConnId -> SConnectionMode c -> AM ()
880+
deleteConnShortLink' c connId cMode =
881+
withConnLock c connId "deleteConnShortLink" $ do
882+
SomeConn _ conn <- withStore c (`getConn` connId)
883+
case (conn, cMode) of
884+
(ContactConnection _ rq, SCMContact) -> deleteQueueLink c rq
885+
(RcvConnection _ rq, SCMInvitation) -> deleteQueueLink c rq
886+
_ -> throwE $ CMD PROHIBITED "deleteConnShortLink: not contact address"
873887

874888
-- TODO [short links] remove 1-time invitation data and link ID from the server after the message is sent.
875889
getConnShortLink' :: forall c. AgentClient -> UserId -> ConnShortLink c -> AM (ConnectionRequestUri c, ConnLinkData c)

src/Simplex/Messaging/Agent/Protocol.hs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1421,6 +1421,11 @@ data CreatedConnLink m = CCLink {connFullLink :: ConnectionRequestUri m, connSho
14211421

14221422
data ACreatedConnLink = forall m. ConnectionModeI m => ACCL (SConnectionMode m) (CreatedConnLink m)
14231423

1424+
instance Eq ACreatedConnLink where
1425+
ACCL m l == ACCL m' l' = case testEquality m m' of
1426+
Just Refl -> l == l'
1427+
_ -> False
1428+
14241429
deriving instance Show ACreatedConnLink
14251430

14261431
data AConnectionLink = forall m. ConnectionModeI m => ACL (SConnectionMode m) (ConnectionLink m)

src/Simplex/Messaging/Server.hs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,6 +1075,11 @@ isContactQueue QueueRec {queueMode, senderKey} = case queueMode of
10751075
Just QMContact -> True
10761076
Nothing -> isNothing senderKey -- for backward compatibility with pre-SKEY contact addresses
10771077

1078+
isSecuredMsgQueue :: QueueRec -> Bool
1079+
isSecuredMsgQueue QueueRec {queueMode, senderKey} = case queueMode of
1080+
Just QMContact -> False
1081+
_ -> isJust senderKey
1082+
10781083
verifyCmdAuthorization :: Maybe (THandleAuth 'TServer, C.CbNonce) -> Maybe TransmissionAuth -> ByteString -> C.APublicAuthKey -> Bool
10791084
verifyCmdAuthorization auth_ tAuth authorized key = maybe False (verify key) tAuth
10801085
where
@@ -1249,13 +1254,14 @@ client
12491254
KEY sKey -> withQueue $ \q _ -> either err (corrId,entId,) <$> secureQueue_ q sKey
12501255
RKEY rKeys -> withQueue $ \q qr -> checkMode QMContact qr $ OK <$$ liftIO (updateKeys (queueStore ms) q rKeys)
12511256
LSET lnkId d ->
1252-
withQueue $ \q qr -> checkContact qr $ liftIO $ case queueData qr of
1253-
Just (lnkId', _) | lnkId' /= lnkId -> pure $ Left AUTH
1254-
_ -> OK <$$ addQueueLinkData (queueStore ms) q lnkId d
1257+
withQueue $ \q qr -> case queueData qr of
1258+
_ | isSecuredMsgQueue qr -> pure $ err AUTH
1259+
Just (lnkId', _) | lnkId' /= lnkId -> pure $ err AUTH -- can't change link ID
1260+
_ -> liftIO $ either err (const ok) <$> addQueueLinkData (queueStore ms) q lnkId d
12551261
LDEL ->
1256-
withQueue $ \q qr -> checkContact qr $ liftIO $ case queueData qr of
1257-
Just _ -> OK <$$ deleteQueueLinkData (queueStore ms) q
1258-
Nothing -> pure $ Right OK
1262+
withQueue $ \q qr -> case queueData qr of
1263+
Just _ -> liftIO $ either err (const ok) <$> deleteQueueLinkData (queueStore ms) q
1264+
Nothing -> pure ok
12591265
NKEY nKey dhKey -> withQueue $ \q _ -> addQueueNotifier_ q nKey dhKey
12601266
NDEL -> withQueue $ \q _ -> deleteQueueNotifier_ q
12611267
OFF -> maybe (pure $ err INTERNAL) suspendQueue_ q_
@@ -1540,6 +1546,8 @@ client
15401546
case C.maxLenBS msgBody of
15411547
Left _ -> pure $ err LARGE_MSG
15421548
Right body -> do
1549+
when (isJust (queueData qr) && isSecuredMsgQueue qr) $ void $ liftIO $
1550+
deleteQueueLinkData (queueStore ms) q
15431551
ServerConfig {messageExpiration, msgIdBytes} <- asks config
15441552
msgId <- randomId' msgIdBytes
15451553
msg_ <- liftIO $ time "SEND" $ runExceptT $ do

tests/AgentTests/FunctionalAPITests.hs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1142,6 +1142,10 @@ testInviationShortLink viaProxy a b =
11421142
Left (SMP _ AUTH) -> pure ()
11431143
r -> liftIO $ expectationFailure ("unexpected result " <> show r)
11441144
runRight $ testJoinConn_ viaProxy True a bId b connReq
1145+
-- invitation link data is removed after the connection is established
1146+
runExceptT (getConnShortLink b 1 shortLink) >>= \case
1147+
Left (SMP _ AUTH) -> pure ()
1148+
r -> liftIO $ expectationFailure ("unexpected result " <> show r)
11451149

11461150
testJoinConn_ :: Bool -> Bool -> AgentClient -> ConnId -> AgentClient -> ConnectionRequestUri c -> ExceptT AgentErrorType IO ()
11471151
testJoinConn_ viaProxy sndSecure a bId b connReq = do
@@ -1213,16 +1217,16 @@ testContactShortLink viaProxy a b =
12131217
exchangeGreetingsViaProxy viaProxy a bId b aId
12141218
-- update user data
12151219
let updatedData = "updated user data"
1216-
shortLink' <- runRight $ setContactShortLink a contactId updatedData Nothing
1220+
shortLink' <- runRight $ setConnShortLink a contactId SCMContact updatedData Nothing
12171221
shortLink' `shouldBe` shortLink
12181222
(connReq4, updatedConnData') <- runRight $ getConnShortLink c 1 shortLink
12191223
connReq4 `shouldBe` connReq
12201224
linkUserData updatedConnData' `shouldBe` updatedData
12211225
-- one more time
1222-
shortLink2 <- runRight $ setContactShortLink a contactId updatedData Nothing
1226+
shortLink2 <- runRight $ setConnShortLink a contactId SCMContact updatedData Nothing
12231227
shortLink2 `shouldBe` shortLink
12241228
-- delete short link
1225-
runRight_ $ deleteContactShortLink a contactId
1229+
runRight_ $ deleteConnShortLink a contactId SCMContact
12261230
Left (SMP _ AUTH) <- runExceptT $ getConnShortLink c 1 shortLink
12271231
pure ()
12281232

@@ -1232,7 +1236,7 @@ testAddContactShortLink viaProxy a b =
12321236
(contactId, CCLink connReq0 Nothing) <- runRight $ A.createConnection a 1 True SCMContact Nothing Nothing CR.IKPQOn SMSubscribe
12331237
Right connReq <- pure $ smpDecode (smpEncode connReq0) --
12341238
let userData = "some user data"
1235-
shortLink <- runRight $ setContactShortLink a contactId userData Nothing
1239+
shortLink <- runRight $ setConnShortLink a contactId SCMContact userData Nothing
12361240
(connReq', connData') <- runRight $ getConnShortLink b 1 shortLink
12371241
strDecode (strEncode shortLink) `shouldBe` Right shortLink
12381242
connReq' `shouldBe` connReq
@@ -1260,7 +1264,7 @@ testAddContactShortLink viaProxy a b =
12601264
exchangeGreetingsViaProxy viaProxy a bId b aId
12611265
-- update user data
12621266
let updatedData = "updated user data"
1263-
shortLink' <- runRight $ setContactShortLink a contactId updatedData Nothing
1267+
shortLink' <- runRight $ setConnShortLink a contactId SCMContact updatedData Nothing
12641268
shortLink' `shouldBe` shortLink
12651269
(connReq4, updatedConnData') <- runRight $ getConnShortLink c 1 shortLink
12661270
connReq4 `shouldBe` connReq
@@ -1291,7 +1295,7 @@ testContactShortLinkRestart ps = withAgentClients2 $ \a b -> do
12911295
connReq' `shouldBe` connReq
12921296
linkUserData connData' `shouldBe` userData
12931297
-- update user data
1294-
shortLink' <- runRight $ setContactShortLink a contactId updatedData Nothing
1298+
shortLink' <- runRight $ setConnShortLink a contactId SCMContact updatedData Nothing
12951299
shortLink' `shouldBe` shortLink
12961300
withSmpServer ps $ do
12971301
(connReq4, updatedConnData') <- runRight $ getConnShortLink b 1 shortLink
@@ -1303,7 +1307,7 @@ testAddContactShortLinkRestart ps = withAgentClients2 $ \a b -> do
13031307
let userData = "some user data"
13041308
((contactId, CCLink connReq0 Nothing), shortLink) <- withSmpServer ps $ runRight $ do
13051309
r@(contactId, _) <- A.createConnection a 1 True SCMContact Nothing Nothing CR.IKPQOn SMOnlyCreate
1306-
(r,) <$> setContactShortLink a contactId userData Nothing
1310+
(r,) <$> setConnShortLink a contactId SCMContact userData Nothing
13071311
Right connReq <- pure $ smpDecode (smpEncode connReq0)
13081312
let updatedData = "updated user data"
13091313
withSmpServer ps $ do
@@ -1312,7 +1316,7 @@ testAddContactShortLinkRestart ps = withAgentClients2 $ \a b -> do
13121316
connReq' `shouldBe` connReq
13131317
linkUserData connData' `shouldBe` userData
13141318
-- update user data
1315-
shortLink' <- runRight $ setContactShortLink a contactId updatedData Nothing
1319+
shortLink' <- runRight $ setConnShortLink a contactId SCMContact updatedData Nothing
13161320
shortLink' `shouldBe` shortLink
13171321
withSmpServer ps $ do
13181322
(connReq4, updatedConnData') <- runRight $ getConnShortLink b 1 shortLink
@@ -1342,14 +1346,14 @@ testOldContactQueueShortLink ps@(_, msType) = withAgentClients2 $ \a b -> do
13421346

13431347
withSmpServer ps $ do
13441348
let userData = "some user data"
1345-
shortLink <- runRight $ setContactShortLink a contactId userData Nothing
1349+
shortLink <- runRight $ setConnShortLink a contactId SCMContact userData Nothing
13461350
(connReq', connData') <- runRight $ getConnShortLink b 1 shortLink
13471351
strDecode (strEncode shortLink) `shouldBe` Right shortLink
13481352
connReq' `shouldBe` connReq
13491353
linkUserData connData' `shouldBe` userData
13501354
-- update user data
13511355
let updatedData = "updated user data"
1352-
shortLink' <- runRight $ setContactShortLink a contactId updatedData Nothing
1356+
shortLink' <- runRight $ setConnShortLink a contactId SCMContact updatedData Nothing
13531357
shortLink' `shouldBe` shortLink
13541358
-- check updated
13551359
(connReq'', updatedConnData') <- runRight $ getConnShortLink b 1 shortLink

tests/ServerTests.hs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,6 +1113,10 @@ testInvQueueLinkData =
11131113
-- can't read link data with LGET
11141114
Resp "2" lnkId' (ERR AUTH) <- sendRecv s ("", "2", lnkId, LGET)
11151115
lnkId' `shouldBe` lnkId
1116+
-- can update link data before it is secured
1117+
let newLD = (EncDataBytes "fixed data", EncDataBytes "updated user data")
1118+
Resp "2a" rId' OK <- signSendRecv r rKey ("2a", rId, LSET lnkId newLD)
1119+
rId' `shouldBe` rId
11161120

11171121
(sPub, sKey) <- atomically $ C.generateAuthKeyPair C.SEd25519 g
11181122

@@ -1128,23 +1132,22 @@ testInvQueueLinkData =
11281132
Resp "5" lnkId2 (LNK sId2 ld') <- signSendRecv s sKey ("5", lnkId, LKEY sPub)
11291133
(lnkId2, lnkId) #== "secures queue and returns link data, same link ID in response"
11301134
(sId2, sId) #== "same sender ID in response"
1131-
(ld', ld) #== "returns stored data"
1135+
(ld', newLD) #== "returns updated stored data"
11321136

11331137
(sPub', sKey') <- atomically $ C.generateAuthKeyPair C.SEd25519 g
11341138
Resp "6" _ err4 <- signSendRecv s sKey' ("6", lnkId, LKEY sPub')
11351139
(err4, ERR AUTH) #== "rejects if secured with different key"
11361140

11371141
Resp "7" _ (LNK sId3 ld2) <- signSendRecv s sKey ("7", lnkId, LKEY sPub)
11381142
sId3 `shouldBe` sId
1139-
ld2 `shouldBe` ld
1143+
ld2 `shouldBe` newLD
11401144

1141-
let newLD = (EncDataBytes "fixed data", EncDataBytes "updated user data")
1142-
Resp "8" rId' (ERR AUTH) <- signSendRecv r rKey ("8", rId, LSET lnkId newLD)
1143-
rId' `shouldBe` rId
1144-
1145-
Resp "9" rId2 (ERR AUTH) <- signSendRecv r rKey ("9", rId, LDEL)
1145+
Resp "8" rId2 (ERR AUTH) <- signSendRecv r rKey ("8", rId, LSET lnkId newLD)
11461146
rId2 `shouldBe` rId
11471147

1148+
Resp "9" rId3 OK <- signSendRecv r rKey ("9", rId, LDEL)
1149+
rId3 `shouldBe` rId
1150+
11481151
testContactQueueLinkData :: SpecWith (ASrvTransport, AStoreType)
11491152
testContactQueueLinkData =
11501153
it "create and access queue short link data for contact address" $ \(ATransport t, msType) ->

0 commit comments

Comments
 (0)