mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-06 18:38:34 +00:00
prevent fallback to PHTest
This commit is contained in:
+1
-1
@@ -21,7 +21,7 @@ constraints: zip +disable-bzip2 +disable-zstd
|
||||
source-repository-package
|
||||
type: git
|
||||
location: https://github.com/simplex-chat/simplexmq.git
|
||||
tag: f66d9ec3952f0ff21c3af5827d05c6d596dad110
|
||||
tag: c7575ddfd2c0196b6eb1f9d0c1c59988c02dfb6d
|
||||
|
||||
source-repository-package
|
||||
type: git
|
||||
|
||||
@@ -293,7 +293,7 @@ An address is a contact address, a business address, or a group link. The link k
|
||||
|
||||
### 14.1 Agent: verification codes
|
||||
|
||||
Merged in simplexmq `5294b7d8`.
|
||||
Merged in simplexmq `5294b7d8`, except the ratchet stored by `newConnToAcceptDR`, its use in `startJoinInvitationDR`, and their tests.
|
||||
|
||||
**Second verification code.** `RatchetInitParams` gains `rcVerifyCodePQ` and `Ratchet` gains `rcVCPQ :: Maybe Str`. The code is derived in `pqX3dh` with a separate HKDF over the same inputs, info `SimpleXVerifyCode`, 32 bytes; the ratchet keys and `rcAD` are unchanged. The code is exported keying material over every handshake input, the KEM included — the fourth of the paper's mitigations. A ratchet created before this change is decoded with `rcVCPQ = Nothing`, and its code is set at the next ratchet resync. The chat uses the AD code, `codeAD`, for the security code and for badge bindings; `codePQ` is stored.
|
||||
|
||||
@@ -331,11 +331,11 @@ data ContactRequestBinding = CRBRatchet ConnVerifyCodes | CRBRequest ByteString
|
||||
- `CRContactUri` with ratchet keys: the same, from the address keys.
|
||||
- `CRContactUri` without keys: the x3dh keys are generated and stored (`generateRcvE2EParams`, `createRatchetX3dhKeys`); the binding is `CRBRequest (sha256 (smpEncode (k1, k2, kem, senderId)))` — the request's public keys and the queue id from the link's `SMPQueueUri`.
|
||||
|
||||
The same pair is returned by `prepareConnectionToAccept`: for `CRInvitation` the ratchet is created from the invitation's keys, for `CRInvitationDR` the ratchet stored in the invitation is used. In `startJoinInvitation` the ratchet is read before one is created, as in the contact path and its retry branch; in `createConnReq` the x3dh keys are read before they are generated, as in `mkJoinInvitation`. The stored ratchet is used by async joins and accepts. The prepare step is local.
|
||||
The same pair is returned by `prepareConnectionToAccept`: for `CRInvitation` the ratchet is created from the invitation's keys, for `CRInvitationDR` the ratchet in the invitation is stored with the connection by `newConnToAcceptDR`. In `startJoinInvitation` and `startJoinInvitationDR` the stored ratchet is used, and a ratchet is created only for a connection without one, as in the contact path and its retry branch; in `createConnReq` the x3dh keys are read before they are generated, as in `mkJoinInvitation`. The stored ratchet is used by async joins and accepts. The prepare step is local.
|
||||
|
||||
**Events.** A `ContactRequestBinding` field in `REQ`: `CRBRequest` in `smpInvitation`, computed from the received `CRInvitationUri` and the queue of the request; `CRBRatchet` in `smpContactRequest`, from the ratchet initialised there. `CONF` and `INFO` are unchanged: the receiver's ratchet is stored before the notification, so its codes are available to `getConnectionVerifyCodes`.
|
||||
|
||||
**Tests.** `DoubleRatchetTests`: the parties agree on `rcVerifyCodePQ`, a substituted KEM key makes it differ while `assocData` matches, and a ratchet stored before the change decodes with `rcVCPQ = Nothing`. `FunctionalAPITests`: both peers get the same codes, and codes cleared from a row are recomputed and saved on the next read.
|
||||
**Tests.** `DoubleRatchetTests`: the parties agree on `rcVerifyCodePQ`, a substituted KEM key makes it differ while `assocData` matches, and a ratchet stored before the change decodes with `rcVCPQ = Nothing`. `FunctionalAPITests`: both peers get the same codes, and codes cleared from a row are recomputed and saved on the next read. For an address with ratchet keys, the codes of the requester's prepare step, of `REQ`, of the acceptor's prepare step and of the stored ratchet are equal before `acceptContact`; an accept of a prepared connection without a ratchet is completed by `acceptContact`.
|
||||
|
||||
### 14.2 Agent: links before link data
|
||||
|
||||
@@ -399,7 +399,7 @@ prepareConnShortLink :: AgentClient -> ConnId -> Maybe CRClientData -> AE (ConnS
|
||||
|
||||
### 14.3 Chat
|
||||
|
||||
In implementation order. At every direct send, a `Nothing` from `connPresHeader`, a header missing from the `connsPresHeaders` map, and a retry without a stored request header are replaced with `PHTest` by `sndPresHeader`. At a send into a group, no badge is presented for a `Nothing` from `groupPresHeader`.
|
||||
In implementation order. A badge is presented only with a header: no badge is presented at a direct send for a connection without codes or for a retry without a stored request header, and at a send into a group for a `Nothing` from `groupPresHeader`. An agent error is returned as a chat error.
|
||||
|
||||
**1. Headers** — `Badges.hs`
|
||||
|
||||
@@ -449,9 +449,8 @@ With `Nothing`, no badge is presented. A badge is presented only when `presentsU
|
||||
- `memberPresHeader :: GroupInfo -> MemberId -> Maybe C.PublicKeyEd25519 -> Maybe ProofPresHeader` — `PHChat (encodeChatBinding CBGroup (smpEncode (publicGroupId, memberId, key)))` in a channel, `PHChat (encodeChatBinding CBGroup (smpEncode (memberId, key)))` in a p2p group, `Nothing` without a key
|
||||
- `memberInfoPresHeader :: GroupInfo -> MemberInfo -> Maybe ProofPresHeader` — in a channel `memberPresHeader` of the id and key in the `MemberInfo`; `Nothing` in a p2p group
|
||||
- `relayInvPresHeader :: GroupRelayInvitation -> Maybe ProofPresHeader` — the channel header of the owner's id and key in the invitation (item 12)
|
||||
- `sndPresHeader :: Maybe ProofPresHeader -> CM ProofPresHeader` — the header, or `PHTest` of 16 random bytes for `Nothing`
|
||||
- `connPresHeader :: Connection -> CM (Maybe ProofPresHeader)` — `directPresHeader . CRBRatchet` of `getConnectionVerifyCodes`; `Nothing` on error
|
||||
- `connsPresHeaders :: [Connection] -> CM (Map ConnId ProofPresHeader)` — the same from `getConnectionsVerifyCodes`
|
||||
- `connPresHeader :: Connection -> CM (Maybe ProofPresHeader)` — `connsPresHeaders` of one connection
|
||||
- `connsPresHeaders :: [Connection] -> CM (Map ConnId ProofPresHeader)` — `directPresHeader . CRBRatchet` of `getConnectionsVerifyCodes`; the map includes the connections with codes
|
||||
|
||||
**5. The binding from prepare steps**
|
||||
|
||||
@@ -476,24 +475,24 @@ ALTER TABLE connections ADD COLUMN pres_header BLOB;
|
||||
|
||||
**7. Direct sends**
|
||||
|
||||
- `joinContact` (`Commands.hs:3979`): the request header, `ProofPresHeader`, is a parameter, set in `connect'`, `joinPreparedConn'` and `connectContactViaAddress` — the `prepareContact` header for a new connection, the stored header for a retry. The badge is presented with `groupPresHeader` in a relay group, and with the request header otherwise.
|
||||
- `connectViaInvitation` (`Commands.hs:3801-3833`) and `connectMemberContact` (`:3388-3415`): the prepare binding for a new connection; `connPresHeader` for a prepared one.
|
||||
- `joinMemberContactAsync` (`Subscriber.hs:3947`): the header is a parameter, set in `xGrpDirectInv` to the `prepareAgentJoin` header.
|
||||
- `acceptContactRequest` (`Internal.hs:974-1005`): the prepare binding for a new connection; `connPresHeader` for an existing one.
|
||||
- `acceptContactRequestAsync` (`Internal.hs:1007-1026`): the profile is built after `prepareAgentAccept`, from its header.
|
||||
- `CONF` replies (`Subscriber.hs:509` direct case, `:628`) and `updateContactPrefs` (`Commands.hs:4108`): `connPresHeader`.
|
||||
- `sendUpdateToContacts` (`Commands.hs:4039-4077`) and `presentUserBadgeToContacts` (`:5212-5228`): one `connsPresHeaders` call per command, when `presentsUserBadge` holds.
|
||||
- `joinContact` (`Commands.hs:3982`): the request header, `Maybe ProofPresHeader`, is a parameter, set in `connect'`, `joinPreparedConn'` and `connectContactViaAddress` — the `prepareContact` header for a new connection, the stored header for a retry. The badge is presented with `groupPresHeader` in a relay group, and with the request header otherwise.
|
||||
- `connectViaInvitation` (`Commands.hs:3808-3845`) and `connectMemberContact` (`:3395-3422`): the prepare binding for a new connection; `connPresHeader` for a prepared one.
|
||||
- `joinMemberContactAsync` (`Subscriber.hs:3956`): the header is a parameter, set in `xGrpDirectInv` to the `prepareAgentJoin` header.
|
||||
- `acceptContactRequest` (`Internal.hs:984-1014`): the prepare binding for a new connection; `connPresHeader` for an existing one.
|
||||
- `acceptContactRequestAsync` (`Internal.hs:1016-1035`): the profile is built after `prepareAgentAccept`, from its header.
|
||||
- `CONF` replies (`Subscriber.hs:507` direct case, `:626`) and `updateContactPrefs` (`Commands.hs:4099`): `connPresHeader`.
|
||||
- `sendUpdateToContacts` (`Commands.hs:4043-4082`) and `presentUserBadgeToContacts` (`:5216-5231`): one `connsPresHeaders` call per command, when `presentsUserBadge` holds.
|
||||
|
||||
**8. Direct receipts**
|
||||
|
||||
- `REQ` (`Subscriber.hs:1404`): `directPresHeader` of the `REQ` binding is a parameter of `profileContactRequest`, passed to `createOrUpdateContactRequest` and to `acceptGroupJoinRequestAsync`.
|
||||
- `processContactProfileUpdate` (`Subscriber.hs:2771`) and `saveConnInfo` (`:3180`, for `createDirectContact`): the header is a parameter, `connPresHeader` of the connection, read by the caller. In the direct case, the badge in the `CONF` reply is presented with the same header.
|
||||
- `REQ` (`Subscriber.hs:1403`): `directPresHeader` of the `REQ` binding is a parameter of `profileContactRequest`, passed to `createOrUpdateContactRequest` and to `acceptGroupJoinRequestAsync`.
|
||||
- `processContactProfileUpdate` (`Subscriber.hs:2767`) and `saveConnInfo` (`:3189`, for `createDirectContact`): the header is a parameter, `connPresHeader` of the connection, read by the caller. In the direct case, the badge in the `CONF` reply is presented with the same header.
|
||||
|
||||
**9. Groups**
|
||||
|
||||
- `groupPresHeader` at every send into a group: `Commands.hs:3998` (`joinContact`, relay group), `:4329`; `Subscriber.hs:506` group case, `:642`, `:836`, `:978`, `:1260`, and `membershipHandshakeProfile` (`:3379-3384`) for `:799`, `:850` and `:3366`; `Internal.hs:2530` (`encodeXGrpAcpt`) and `:2715`.
|
||||
- `acceptGroupJoinRequestAsync` (`Internal.hs:1028`): the expected header is a new parameter, passed to `createJoiningMember` and `updateMemberProfile` — in `memberJoinRequestViaRelay`, `memberPresHeader` of the joining member's id and the key in `XMember` when the message signature is verified with it. `Nothing` is passed to `createJoiningMember` in `acceptGroupJoinSendRejectAsync`.
|
||||
- Host `INFO` with `XInfo` (`Subscriber.hs:865-872`): after `storeMemberKey`, the profile is stored by `processMemberProfileUpdate` with `signedMemberPresHeader`, under the member's key.
|
||||
- `groupPresHeader` at every send into a group: `Commands.hs:4002` (`joinContact`, relay group), `:4332`; `Subscriber.hs:506` group case, `:639`, `:833`, `:975`, `:1257`, and `membershipHandshakeProfile` (`:3388-3393`) for `:796`, `:847` and `:3375`; `Internal.hs:2535` (`encodeXGrpAcpt`) and `:2719`.
|
||||
- `acceptGroupJoinRequestAsync` (`Internal.hs:1037`): the expected header is a new parameter, passed to `createJoiningMember` and `updateMemberProfile` — in `memberJoinRequestViaRelay`, `memberPresHeader` of the joining member's id and the key in `XMember` when the message signature is verified with it. `Nothing` is passed to `createJoiningMember` in `acceptGroupJoinSendRejectAsync`.
|
||||
- Host `INFO` with `XInfo` (`Subscriber.hs:862-869`): after `storeMemberKey`, the profile is stored by `processMemberProfileUpdate` with `signedMemberPresHeader`, under the member's key.
|
||||
- The profile is also stored by `processMemberProfileUpdate` when the received proof is accepted and differs from the stored member proof in its header or its disclosed information, and when a profile without a proof is received for a member with a stored proof.
|
||||
- Introductions:
|
||||
- In `memberInfo` (`Internal.hs:1335`) the stored proof (item 11) is included when `acceptedProof` holds under `memberPresHeader` of the member's id and stored key in a channel, and under `Nothing` in a p2p group: in a p2p group only a `PHTest` proof is included.
|
||||
@@ -589,7 +588,7 @@ Postgres: `BIGINT`, `BYTEA` and `TIMESTAMPTZ`. Both schema dumps and `chat_query
|
||||
- Channel: a relay invitation with an owner key that differs from the link data is failed by the relay, and the relay stays invited (`testChannelAddRelayOwnerKeyMismatch`).
|
||||
- Link data: the badge is shown from an invitation link under `PHLink`, an address, and an address that gets its first short link.
|
||||
|
||||
`PHTest` is still sent by released clients and, through `sndPresHeader`, on a retry of a connection prepared before this change.
|
||||
`PHTest` proofs are generated only by released clients. For a connection prepared by a released client and joined after the update, the badge is presented only when its ratchet was stored by an earlier attempt; a retried request to an address prepared by a released client is sent without a badge.
|
||||
|
||||
## Out of scope
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"https://github.com/simplex-chat/simplexmq.git"."f66d9ec3952f0ff21c3af5827d05c6d596dad110" = "1dfz8f5fcr5dzyc15m523zl932sxdrch8snhl4vj9zgw9bgyrirx";
|
||||
"https://github.com/simplex-chat/simplexmq.git"."c7575ddfd2c0196b6eb1f9d0c1c59988c02dfb6d" = "0qy9jjg6vizh77vhm4xvnb7sfqpmb7d53zspmk7a4mkbv48354kz";
|
||||
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
|
||||
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";
|
||||
"https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl";
|
||||
|
||||
@@ -272,7 +272,14 @@ maxSndXFTPFileSize lims now = \case
|
||||
-- presentation, not bound to any context.
|
||||
-- PHUnknown is the forward-compat catch-all for tags this version does not interpret.
|
||||
|
||||
data ProofPresHeaderTag = PHTestTag | PHChatTag | PHFileInvTag | PHFileDescrTag | PHRequestTag | PHLinkTag | PHUnknownTag Char
|
||||
data ProofPresHeaderTag
|
||||
= PHTestTag -- random nonce: released clients
|
||||
| PHChatTag -- direct chat ratchet, group member key
|
||||
| PHFileInvTag -- file invitation
|
||||
| PHFileDescrTag -- file description
|
||||
| PHRequestTag -- request to address without ratchet keys
|
||||
| PHLinkTag -- invitation and address link data
|
||||
| PHUnknownTag Char -- tags of newer versions
|
||||
|
||||
instance StrEncoding ProofPresHeaderTag where
|
||||
strEncode = B.singleton . \case
|
||||
|
||||
@@ -3401,7 +3401,7 @@ processChatCommand cxt nm = \case
|
||||
-- so incognito profile can be attached to it and be visible in UI before accepting
|
||||
Nothing -> joinNewConn subMode
|
||||
Just conn@Connection {connStatus} -> case connStatus of
|
||||
ConnPrepared -> joinPreparedConn subMode conn =<< sndPresHeader =<< connPresHeader conn
|
||||
ConnPrepared -> joinPreparedConn subMode conn =<< connPresHeader conn
|
||||
_ -> throwChatError $ CEException "connection already started (past prepared status)"
|
||||
where
|
||||
joinNewConn subMode = do
|
||||
@@ -3412,10 +3412,10 @@ processChatCommand cxt nm = \case
|
||||
conn <- withStore $ \db -> do
|
||||
connId <- liftIO $ createMemberContactConn db user acId Nothing gInfo mConn ConnPrepared contactId subMode
|
||||
getConnectionById db cxt user connId
|
||||
joinPreparedConn subMode conn $ directPresHeader binding
|
||||
joinPreparedConn subMode conn presHeader = do
|
||||
joinPreparedConn subMode conn $ Just $ directPresHeader binding
|
||||
joinPreparedConn subMode conn presHeader_ = do
|
||||
-- [incognito] send membership incognito profile
|
||||
p <- presentUserBadge user (incognitoMembershipProfile gInfo) (Just presHeader) $ userProfileDirect user (fromLocalProfile <$> incognitoMembershipProfile gInfo) Nothing True
|
||||
p <- presentUserBadge user (incognitoMembershipProfile gInfo) presHeader_ $ userProfileDirect user (fromLocalProfile <$> incognitoMembershipProfile gInfo) Nothing True
|
||||
dm <- encodeConnInfo $ XInfo p Nothing
|
||||
sqSecured <- withAgent $ \a -> joinConnection a nm (aUserId user) (aConnId conn) True cReq dm PQSupportOff subMode
|
||||
let newStatus = if sqSecured then ConnSndReady else ConnJoined
|
||||
@@ -3820,8 +3820,7 @@ processChatCommand cxt nm = \case
|
||||
| connStatus == ConnNew && contactConnInitiated -> joinNewConn chatV -- own connection link
|
||||
| connStatus == ConnPrepared -> do -- retrying join after error
|
||||
localIncognitoProfile <- forM customUserProfileId $ \pId -> withFastStore $ \db -> getProfileById db userId pId
|
||||
presHeader <- sndPresHeader =<< connPresHeader conn
|
||||
joinPreparedConn conn (fromLocalProfile <$> localIncognitoProfile) presHeader
|
||||
joinPreparedConn conn (fromLocalProfile <$> localIncognitoProfile) =<< connPresHeader conn
|
||||
Just ent -> throwCmdError $ "connection is not RcvDirectMsgConnection: " <> show (connEntityInfo ent)
|
||||
where
|
||||
-- all supported versions support PQ encryption
|
||||
@@ -3832,9 +3831,9 @@ processChatCommand cxt nm = \case
|
||||
(connId, binding) <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq pqSup'
|
||||
let ccLink = CCLink cReq $ serverShortLink <$> sLnk_
|
||||
conn <- withFastStore' $ \db -> createDirectConnection' db userId connId ccLink contactId_ ConnPrepared incognitoProfile subMode chatV pqSup'
|
||||
joinPreparedConn conn incognitoProfile $ directPresHeader binding
|
||||
joinPreparedConn conn incognitoProfile presHeader = do
|
||||
profileToSend <- presentUserBadge user incognitoProfile (Just presHeader) $ userProfileDirect user incognitoProfile Nothing True
|
||||
joinPreparedConn conn incognitoProfile $ Just $ directPresHeader binding
|
||||
joinPreparedConn conn incognitoProfile presHeader_ = do
|
||||
profileToSend <- presentUserBadge user incognitoProfile presHeader_ $ userProfileDirect user incognitoProfile Nothing True
|
||||
dm <- encodeConnInfoPQ pqSup' $ XInfo profileToSend Nothing
|
||||
sqSecured <- withAgent $ \a -> joinConnection a nm (aUserId user) (aConnId conn) True cReq dm pqSup' subMode
|
||||
let newStatus = if sqSecured then ConnSndReady else ConnJoined
|
||||
@@ -3889,9 +3888,8 @@ processChatCommand cxt nm = \case
|
||||
-- TODO [relays] member: refactor joinContact and up avoiding parallel ifs, xContactId is not used
|
||||
xContactId <- mkXContactId xContactId_
|
||||
((cReq', reqHeader_), localIncognitoProfile) <- withFastStore $ \db -> (,) <$> getConnReqContact db connId <*> forM customUserProfileId (getProfileById db userId)
|
||||
reqHeader <- sndPresHeader reqHeader_
|
||||
let incognitoProfile = fromLocalProfile <$> localIncognitoProfile
|
||||
conn' <- joinContact user conn cReq' incognitoProfile reqHeader xContactId welcomeSharedMsgId msg_ gInfo_ relayMemberId_ PQSupportOn
|
||||
conn' <- joinContact user conn cReq' incognitoProfile reqHeader_ xContactId welcomeSharedMsgId msg_ gInfo_ relayMemberId_ PQSupportOn
|
||||
pure $ CVRSentInvitation conn' incognitoProfile
|
||||
connect' groupLinkId xContactId_ gInfo_ = do
|
||||
let inGroup = isJust groupLinkId
|
||||
@@ -3906,7 +3904,7 @@ processChatCommand cxt nm = \case
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
let sLnk' = serverShortLink <$> sLnk
|
||||
conn <- withFastStore' $ \db -> createConnReqConnection db userId connId preparedEntity_ cReq reqHeader cReqHash1 sLnk' xContactId incognitoProfile_ groupLinkId subMode chatV pqSup
|
||||
conn' <- joinContact user conn cReq incognitoProfile reqHeader xContactId welcomeSharedMsgId msg_ gInfo_ relayMemberId_ pqSup
|
||||
conn' <- joinContact user conn cReq incognitoProfile (Just reqHeader) xContactId welcomeSharedMsgId msg_ gInfo_ relayMemberId_ pqSup
|
||||
pure $ CVRSentInvitation conn' incognitoProfile
|
||||
connectContactViaAddress :: User -> IncognitoEnabled -> Contact -> CreatedLinkContact -> CM ChatResponse
|
||||
connectContactViaAddress user@User {userId} incognito ct@Contact {contactId, activeConn} (CCLink cReq shortLink) =
|
||||
@@ -3921,7 +3919,7 @@ processChatCommand cxt nm = \case
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
let cReqHash = contactCReqHash cReq
|
||||
conn <- withFastStore' $ \db -> createConnReqConnection db userId connId (Just $ PCEContact ct) cReq reqHeader cReqHash shortLink newXContactId (NewIncognito <$> incognitoProfile) Nothing subMode chatV pqSup
|
||||
void $ joinContact user conn cReq incognitoProfile reqHeader newXContactId Nothing Nothing Nothing Nothing pqSup
|
||||
void $ joinContact user conn cReq incognitoProfile (Just reqHeader) newXContactId Nothing Nothing Nothing Nothing pqSup
|
||||
ct' <- withStore $ \db -> getContact db cxt user contactId
|
||||
pure $ CRSentInvitationToContact user ct' incognitoProfile
|
||||
Just conn@Connection {connId, connStatus, xContactId = xContactId_, customUserProfileId} -> case connStatus of
|
||||
@@ -3929,9 +3927,8 @@ processChatCommand cxt nm = \case
|
||||
when (incognito /= isJust customUserProfileId) $ throwCmdError "incognito mode is different from prepared connection"
|
||||
xContactId <- mkXContactId xContactId_
|
||||
((cReq', reqHeader_), localIncognitoProfile) <- withFastStore $ \db -> (,) <$> getConnReqContact db connId <*> forM customUserProfileId (getProfileById db userId)
|
||||
reqHeader <- sndPresHeader reqHeader_
|
||||
let incognitoProfile = fromLocalProfile <$> localIncognitoProfile
|
||||
void $ joinContact user conn cReq' incognitoProfile reqHeader xContactId Nothing Nothing Nothing Nothing PQSupportOn
|
||||
void $ joinContact user conn cReq' incognitoProfile reqHeader_ xContactId Nothing Nothing Nothing Nothing PQSupportOn
|
||||
ct' <- withStore $ \db -> getContact db cxt user contactId
|
||||
pure $ CRSentInvitationToContact user ct' incognitoProfile
|
||||
_ -> throwCmdError "contact already has connection"
|
||||
@@ -3982,8 +3979,8 @@ processChatCommand cxt nm = \case
|
||||
pure (connId, chatV, directPresHeader binding)
|
||||
mkXContactId :: Maybe XContactId -> CM XContactId
|
||||
mkXContactId = maybe (XContactId <$> drgRandomBytes 16) pure
|
||||
joinContact :: User -> Connection -> ConnReqContact -> Maybe Profile -> ProofPresHeader -> XContactId -> Maybe SharedMsgId -> Maybe (SharedMsgId, MsgContent) -> Maybe (Maybe GroupInfoKeys) -> Maybe MemberId -> PQSupport -> CM Connection
|
||||
joinContact user conn cReq incognitoProfile reqHeader xContactId welcomeSharedMsgId msg_ gInfo_ relayMemberId_ pqSup = do
|
||||
joinContact :: User -> Connection -> ConnReqContact -> Maybe Profile -> Maybe ProofPresHeader -> XContactId -> Maybe SharedMsgId -> Maybe (SharedMsgId, MsgContent) -> Maybe (Maybe GroupInfoKeys) -> Maybe MemberId -> PQSupport -> CM Connection
|
||||
joinContact user conn cReq incognitoProfile reqHeader_ xContactId welcomeSharedMsgId msg_ gInfo_ relayMemberId_ pqSup = do
|
||||
-- gInfo_ is Maybe (Maybe GroupInfo), where Just Nothing means "some unknown group", e.g. when joining via link without profile
|
||||
profileToSend <-
|
||||
presentUserBadge user incognitoProfile presHeader_ $ case gInfo_ of
|
||||
@@ -4003,7 +4000,7 @@ processChatCommand cxt nm = \case
|
||||
where
|
||||
presHeader_ = case gInfo_ of
|
||||
Just (Just (GIK g _)) | useRelays' g -> groupPresHeader g
|
||||
_ -> Just reqHeader
|
||||
_ -> reqHeader_
|
||||
contactMember :: Contact -> [GroupMember] -> Maybe GroupMember
|
||||
contactMember Contact {contactId} =
|
||||
find $ \GroupMember {memberContactId = cId, memberStatus = s} ->
|
||||
@@ -4077,8 +4074,7 @@ processChatCommand cxt nm = \case
|
||||
-- non-incognito (filtered above), so the user's badge is presented; a profile update keeps the badge instead of clearing it
|
||||
ctSndEvent :: Map ConnId ProofPresHeader -> ChangedProfileContact -> CM (ConnOrGroupId, Maybe MsgSigning, ChatMsgEvent 'Json)
|
||||
ctSndEvent presHeaders ChangedProfileContact {mergedProfile', conn = conn@Connection {connId}} = do
|
||||
presHeader <- sndPresHeader $ M.lookup (aConnId conn) presHeaders
|
||||
p'' <- presentUserBadge user' Nothing (Just presHeader) mergedProfile'
|
||||
p'' <- presentUserBadge user' Nothing (M.lookup (aConnId conn) presHeaders) mergedProfile'
|
||||
pure (ConnectionId connId, Nothing, XInfo p'' Nothing)
|
||||
ctMsgReq :: ChangedProfileContact -> Either ChatError SndMessage -> Either ChatError ChatMsgReq
|
||||
ctMsgReq ChangedProfileContact {conn} =
|
||||
@@ -4112,8 +4108,8 @@ processChatCommand cxt nm = \case
|
||||
mergedProfile' = userProfileDirect user (fromLocalProfile <$> incognitoProfile) (Just ct') False
|
||||
when (mergedProfile' /= mergedProfile) $
|
||||
withContactLock "updateContactPrefs" (contactId' ct) $ do
|
||||
presHeader <- sndPresHeader =<< connPresHeader conn
|
||||
p <- presentUserBadge user incognitoProfile (Just presHeader) mergedProfile'
|
||||
presHeader_ <- connPresHeader conn
|
||||
p <- presentUserBadge user incognitoProfile presHeader_ mergedProfile'
|
||||
void (sendDirectContactMessage user ct' $ XInfo p Nothing) `catchAllErrors` eToView
|
||||
lift . when (directOrUsed ct') $ createSndFeatureItems user ct ct'
|
||||
pure $ CRContactPrefsUpdated user ct ct'
|
||||
@@ -5231,8 +5227,7 @@ presentUserBadgeToContacts user'@User {userId, profile = LocalProfile {localBadg
|
||||
presHeaders <- if presentsUserBadge user' then connsPresHeaders $ map snd sendConns else pure M.empty
|
||||
withChatLock "presentUserBadge" $ forM_ sendConns $ \(ct, conn) -> do
|
||||
let ct' = updateMergedPreferences user' ct
|
||||
presHeader <- sndPresHeader $ M.lookup (aConnId conn) presHeaders
|
||||
p <- presentUserBadge user' Nothing (Just presHeader) $ userProfileDirect user' Nothing (Just ct') False
|
||||
p <- presentUserBadge user' Nothing (M.lookup (aConnId conn) presHeaders) $ userProfileDirect user' Nothing (Just ct') False
|
||||
void (sendDirectContactMessage user' ct' (XInfo p Nothing)) `catchAllErrors` eToView
|
||||
|
||||
-- | The check character is verified before anything leaves the device, and the signing keys are
|
||||
|
||||
@@ -988,13 +988,13 @@ acceptContactRequest nm user@User {userId} UserContactRequest {agentInvitationId
|
||||
pqSup' = pqSup `CR.pqSupportAnd` pqSupport
|
||||
cxt <- chatStoreCxt
|
||||
let chatV = vr cxt `peerConnChatVersion` cReqChatVRange
|
||||
(ct, conn, incognitoProfile, presHeader) <- case contactId_ of
|
||||
(ct, conn, incognitoProfile, presHeader_) <- case contactId_ of
|
||||
Nothing -> do
|
||||
incognitoProfile <- if incognito then Just . NewIncognito <$> liftIO generateRandomProfile else pure Nothing
|
||||
(connId, binding) <- withAgent $ \a -> prepareConnectionToAccept a (aUserId user) True invId pqSup'
|
||||
(ct, conn) <- withStore' $ \db ->
|
||||
createContactFromRequest db user userContactLinkId_ connId chatV cReqChatVRange cName profileId cp xContactId incognitoProfile subMode pqSup' False
|
||||
pure (ct, conn, incognitoProfile, directPresHeader binding)
|
||||
pure (ct, conn, incognitoProfile, Just $ directPresHeader binding)
|
||||
Just contactId -> do
|
||||
ct <- withFastStore $ \db -> getContact db cxt user contactId
|
||||
case contactConn ct of
|
||||
@@ -1005,12 +1005,11 @@ acceptContactRequest nm user@User {userId} UserContactRequest {agentInvitationId
|
||||
conn <- withStore' $ \db -> do
|
||||
forM_ xContactId $ \xcId -> setContactAcceptedXContactId db ct xcId
|
||||
createAcceptedContactConn db user userContactLinkId_ contactId connId chatV cReqChatVRange pqSup' incognitoProfile subMode currentTs
|
||||
pure (ct {activeConn = Just conn} :: Contact, conn, incognitoProfile, directPresHeader binding)
|
||||
pure (ct {activeConn = Just conn} :: Contact, conn, incognitoProfile, Just $ directPresHeader binding)
|
||||
Just conn@Connection {customUserProfileId} -> do
|
||||
incognitoProfile <- forM customUserProfileId $ \pId -> withFastStore $ \db -> getProfileById db userId pId
|
||||
presHeader <- sndPresHeader =<< connPresHeader conn
|
||||
pure (ct, conn, ExistingIncognito <$> incognitoProfile, presHeader)
|
||||
profileToSend <- presentUserBadge user incognitoProfile (Just presHeader) $ userProfileDirect user (fromIncognitoProfile <$> incognitoProfile) (Just ct) True
|
||||
(ct, conn, ExistingIncognito <$> incognitoProfile,) <$> connPresHeader conn
|
||||
profileToSend <- presentUserBadge user incognitoProfile presHeader_ $ userProfileDirect user (fromIncognitoProfile <$> incognitoProfile) (Just ct) True
|
||||
dm <- encodeConnInfoPQ pqSup' $ XInfo profileToSend Nothing
|
||||
(ct,conn,) <$> withAgent (\a -> acceptContact a nm (aUserId user) (aConnId conn) True invId dm pqSup' subMode)
|
||||
|
||||
@@ -2281,14 +2280,11 @@ relayInvPresHeader :: GroupRelayInvitation -> Maybe ProofPresHeader
|
||||
relayInvPresHeader GroupRelayInvitation {fromMember = MemberIdRole {memberId}, publicGroupId, fromMemberKey} =
|
||||
(\gId (MemberKey k) -> PHChat $ encodeChatBinding CBGroup $ smpEncode (gId, memberId, k)) <$> publicGroupId <*> fromMemberKey
|
||||
|
||||
sndPresHeader :: Maybe ProofPresHeader -> CM ProofPresHeader
|
||||
sndPresHeader = maybe (PHTest <$> drgRandomBytes 16) pure
|
||||
|
||||
connPresHeader :: Connection -> CM (Maybe ProofPresHeader)
|
||||
connPresHeader conn = eitherToMaybe <$> tryAllErrors (directPresHeader . CRBRatchet <$> withAgent (`getConnectionVerifyCodes` aConnId conn))
|
||||
connPresHeader conn = M.lookup (aConnId conn) <$> connsPresHeaders [conn]
|
||||
|
||||
connsPresHeaders :: [Connection] -> CM (Map ConnId ProofPresHeader)
|
||||
connsPresHeaders conns = either (const M.empty) (M.map (directPresHeader . CRBRatchet)) <$> tryAllErrors (withAgent (`getConnectionsVerifyCodes` map aConnId conns))
|
||||
connsPresHeaders conns = M.map (directPresHeader . CRBRatchet) <$> withAgent (`getConnectionsVerifyCodes` map aConnId conns)
|
||||
|
||||
-- receiving side of contact/invitation link data: verify the badge proof from the link profile
|
||||
-- and set the crypto-free display badge for the UI (the raw proof stays in profile for APIPrepareContact)
|
||||
|
||||
@@ -504,9 +504,7 @@ processAgentMessageConn cxt user@User {userId} entity gks_ corrId agentConnId ag
|
||||
incognitoProfile <- forM customUserProfileId $ \profileId -> withStore (\db -> getProfileById db userId profileId)
|
||||
profileToSend <- case gInfo_ of
|
||||
Just (GIK gInfo _) -> presentUserBadge user incognitoProfile (groupPresHeader gInfo) $ userProfileInGroup user gInfo (fromLocalProfile <$> incognitoProfile)
|
||||
Nothing -> do
|
||||
presHeader <- sndPresHeader presHeader_
|
||||
presentUserBadge user incognitoProfile (Just presHeader) $ userProfileDirect user (fromLocalProfile <$> incognitoProfile) Nothing True
|
||||
Nothing -> presentUserBadge user incognitoProfile presHeader_ $ userProfileDirect user (fromLocalProfile <$> incognitoProfile) Nothing True
|
||||
-- [async agent commands] no continuation needed, but command should be asynchronous for stability
|
||||
allowAgentConnectionAsync user conn'' confId gInfo_ $ XInfo profileToSend ((\(GIK _ gks) -> groupMemberKey gks) <$> gInfo_)
|
||||
INFO pqSupport connInfo -> do
|
||||
@@ -625,8 +623,7 @@ processAgentMessageConn cxt user@User {userId} entity gks_ corrId agentConnId ag
|
||||
ct' <- processContactProfileUpdate ct presHeader_ profile False `catchAllErrors` const (pure ct)
|
||||
-- [incognito] send incognito profile
|
||||
incognitoProfile <- forM customUserProfileId $ \profileId -> withStore $ \db -> getProfileById db userId profileId
|
||||
presHeader <- sndPresHeader presHeader_
|
||||
p <- presentUserBadge user incognitoProfile (Just presHeader) $ userProfileDirect user (fromLocalProfile <$> incognitoProfile) (Just ct') True
|
||||
p <- presentUserBadge user incognitoProfile presHeader_ $ userProfileDirect user (fromLocalProfile <$> incognitoProfile) (Just ct') True
|
||||
allowAgentConnectionAsync user conn'' confId Nothing $ XInfo p Nothing
|
||||
void $ withStore' $ \db -> resetMemberContactFields db ct'
|
||||
XGrpLinkInv glInv -> do
|
||||
|
||||
@@ -671,6 +671,13 @@ Query:
|
||||
|
||||
Plan:
|
||||
|
||||
Query:
|
||||
INSERT INTO ratchets (conn_id, ratchet_state, rc_verify_code_ad, rc_verify_code_pq)
|
||||
VALUES (?, ?, ?, ?)
|
||||
ON CONFLICT (conn_id) DO NOTHING
|
||||
|
||||
Plan:
|
||||
|
||||
Query:
|
||||
INSERT INTO ratchets (conn_id, ratchet_state, rc_verify_code_ad, rc_verify_code_pq)
|
||||
VALUES (?, ?, ?, ?)
|
||||
|
||||
Reference in New Issue
Block a user