sign address card when shared by owner

This commit is contained in:
Evgeny @ SimpleX Chat
2026-07-15 08:48:24 +00:00
parent 9d37a6efc3
commit 0f7c5ffb7a
2 changed files with 62 additions and 26 deletions
+26 -23
View File
@@ -1144,39 +1144,23 @@ processChatCommand cxt nm = \case
_ -> Nothing
ownerSig <-
pure signingKeys $>>= \GroupKeys {memberPrivKey} ->
mkLinkOwnerSig memberPrivKey groupLink memberId <$$> shareChatBinding user toSendRef
mkLinkOwnerSig memberPrivKey groupLink (Just memberId) <$$> shareChatBinding user toSendRef
let text = safeDecodeUtf8 $ strEncode groupLink
pure $ CRChatMsgContent user MCChat {text, chatLink = MCLGroup groupLink gp, ownerSig}
where
mkLinkOwnerSig :: ConnectionModeI m => C.PrivateKeyEd25519 -> ConnShortLink m -> MemberId -> (ChatBinding, ByteString) -> LinkOwnerSig
mkLinkOwnerSig privKey connLink MemberId {unMemberId} (cbTag, bindingData) =
let ownerId = Just $ B64UrlByteString unMemberId
cb = encodeChatBinding cbTag bindingData
ownerSig = C.sign' privKey $ cb <> smpEncode connLink
in LinkOwnerSig {ownerId, chatBinding = B64UrlByteString cb, ownerSig}
shareChatBinding :: User -> SendRef -> CM (Maybe (ChatBinding, ByteString))
shareChatBinding u = \case
SRDirect contactId -> do
ct <- withFastStore $ \db -> getContact db cxt u contactId
forM (contactConn ct) $ \conn ->
(CBDirect,) <$> withAgent (`getConnectionRatchetAdHash` aConnId conn)
SRGroup toGroupId _ asGroup -> do
GroupInfo {groupProfile = GroupProfile {publicGroup}, membership = m} <- withFastStore $ \db -> getGroupInfo db cxt u toGroupId
pure $ mkBinding m <$> publicGroup
where
mkBinding GroupMember {memberId} PublicGroupProfile {publicGroupId = pgId}
| asGroup = (CBChannel, smpEncode pgId)
| otherwise = (CBGroup, smpEncode (pgId, memberId))
APIShareChatMsgContent _ _ -> throwCmdError "sharing is only supported for public groups"
APIShareMyAddress _ -> withUser $ \user -> do
APIShareMyAddress toSendRef -> withUser $ \user -> do
UserContactLink {connLinkContact = CCLink _ sl_, addressSettings} <- withFastStore (`getUserAddress` user)
case sl_ of
Nothing -> throwCmdError "your address has no short link to share"
Just connLink -> do
conn <- withFastStore $ \db -> getUserAddressConnection db cxt user
ownerSig <-
withAgent (`getConnLinkPrivKey` aConnId conn) $>>= \privKey ->
mkLinkOwnerSig privKey connLink Nothing <$$> shareChatBinding user toSendRef
let business = businessAddress addressSettings
profile = userProfileDirect user Nothing Nothing True
text = safeDecodeUtf8 $ strEncode connLink
pure $ CRChatMsgContent user MCChat {text, chatLink = MCLContact {connLink, profile, business}, ownerSig = Nothing}
pure $ CRChatMsgContent user MCChat {text, chatLink = MCLContact {connLink, profile, business}, ownerSig}
APIUserRead userId -> withUserId userId $ \user -> withFastStore' (`setUserChatsRead` user) >> ok user
UserRead -> withUser $ \User {userId} -> processChatCommand cxt nm $ APIUserRead userId
APIChatRead chatRef@(ChatRef cType chatId scope_) -> withUser $ \_ -> case cType of
@@ -4537,6 +4521,25 @@ processChatCommand cxt nm = \case
serverShortLink = \case
CSLInvitation _ srv lnkId linkKey -> CSLInvitation SLSServer srv lnkId linkKey
CSLContact _ ct srv linkKey -> CSLContact SLSServer ct srv linkKey
mkLinkOwnerSig :: ConnectionModeI m => C.PrivateKeyEd25519 -> ConnShortLink m -> Maybe MemberId -> (ChatBinding, ByteString) -> LinkOwnerSig
mkLinkOwnerSig privKey connLink ownerMemberId (cbTag, bindingData) =
let ownerId = (\MemberId {unMemberId} -> B64UrlByteString unMemberId) <$> ownerMemberId
cb = encodeChatBinding cbTag bindingData
ownerSig = C.sign' privKey $ cb <> smpEncode connLink
in LinkOwnerSig {ownerId, chatBinding = B64UrlByteString cb, ownerSig}
shareChatBinding :: User -> SendRef -> CM (Maybe (ChatBinding, ByteString))
shareChatBinding u = \case
SRDirect contactId -> do
ct <- withFastStore $ \db -> getContact db cxt u contactId
forM (contactConn ct) $ \conn ->
(CBDirect,) <$> withAgent (`getConnectionRatchetAdHash` aConnId conn)
SRGroup toGroupId _ asGroup -> do
GroupInfo {groupProfile = GroupProfile {publicGroup}, membership = m} <- withFastStore $ \db -> getGroupInfo db cxt u toGroupId
pure $ mkBinding m <$> publicGroup
where
mkBinding GroupMember {memberId} PublicGroupProfile {publicGroupId = pgId}
| asGroup = (CBChannel, smpEncode pgId)
| otherwise = (CBGroup, smpEncode (pgId, memberId))
verifyLinkOwner :: ConnectionModeI m => C.PublicKeyEd25519 -> [OwnerAuth] -> ConnShortLink m -> Maybe LinkOwnerSig -> Maybe OwnerVerification
verifyLinkOwner rootKey owners connLink =
fmap $ \LinkOwnerSig {ownerId, chatBinding = B64UrlByteString bindingBytes, ownerSig} ->
+36 -3
View File
@@ -26,9 +26,9 @@ import qualified Data.Map.Strict as M
import Simplex.Chat.Badges (BadgeCredential, BadgeInfo (..), BadgePurchase (..), BadgeRequest (..), BadgeType (..), generateMasterKey, issueBadge, verifyPayment)
import Simplex.Chat.Controller (ChatConfig (..), ChatController (..), ChatHooks (..), defaultChatHooks, mkStoreCxt)
import Simplex.Chat.Options (ChatOpts (..), CoreChatOpts (..))
import Simplex.Chat.Protocol (currentChatVersion)
import Simplex.Chat.Protocol (LinkOwnerSig, MsgChatLink (..), MsgContent (..), currentChatVersion)
import Simplex.Chat.Store.Shared (createContact)
import Simplex.Chat.Types (ConnStatus (..), Profile (..), GroupRejectionReason (..))
import Simplex.Chat.Types (ConnStatus (..), Profile (..), GroupRejectionReason (..), profileFromName)
import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Crypto.BBS (BBSPublicKey, BBSSecretKey, bbsKeyGen)
import Simplex.Chat.Types.Shared (GroupMemberRole (..))
@@ -38,7 +38,7 @@ import Simplex.Messaging.Agent.RetryInterval
import Simplex.Messaging.Encoding.String (StrEncoding (..))
import Simplex.Messaging.Server.Env.STM hiding (subscriptions)
import Simplex.Messaging.Transport
import Simplex.Messaging.Util (encodeJSON)
import Simplex.Messaging.Util (decodeJSON, encodeJSON)
import System.Directory (copyFile, createDirectoryIfMissing)
import Test.Hspec hiding (it)
@@ -121,6 +121,7 @@ chatProfileTests = do
it "should connect via one-time invitation" testShortLinkInvitation
it "should plan and connect via one-time invitation" testPlanShortLinkInvitation
it "should connect via contact address" testShortLinkContactAddress
it "should share contact address via chat" testShareAddressViaChat
it "should join group" testShortLinkJoinGroup
describe "short links with attached data" shortLinkTests
describe "client services" $ do
@@ -3026,6 +3027,38 @@ testPlanShortLinkInvitation =
slSimplexScheme :: String -> String
slSimplexScheme sl = T.unpack $ T.replace "https://localhost/" "simplex:/" (T.pack sl) <> "?h=localhost"
testShareAddressViaChat :: HasCallStack => TestParams -> IO ()
testShareAddressViaChat =
testChat3 aliceProfile bobProfile cathProfile $ \alice bob cath -> do
alice ##> "/ad"
_ <- getContactLinks alice True
connectUsers alice bob
connectUsers bob cath
-- alice shares her signed address card to bob
alice ##> "/share address @bob"
alice <# "@bob contact address of @alice (signed):"
_ <- getTermLine alice -- link
_ <- getTermLine alice -- owner signature (testView)
bob <# "alice> contact address of @alice (signed):"
bLink <- getTermLine bob
bSig <- getTermLine bob
-- bob verifies alice's owner signature
bob ##> ("/_connect plan 1 " <> bLink <> " sig=" <> bSig)
bob <## "contact address: ok to connect"
bob <## "owner signature: verified"
_ <- getTermLine bob -- link data
-- bob replays alice's signed card to cath: the binding was alice->bob, so cath strips the signature
let sig = maybe (error "bad sig") id (decodeJSON (T.pack bSig) :: Maybe LinkOwnerSig)
cLink = either error id $ strDecode (B.pack bLink)
mc = MCChat (T.pack bLink) (MCLContact cLink (profileFromName "alice") False) (Just sig)
cm = "{\"msgContent\":" <> T.unpack (encodeJSON mc) <> "}"
bob ##> ("/_send @3 json [" <> cm <> "]")
bob <# "@cath contact address of @alice (signed):"
_ <- getTermLine bob -- link
_ <- getTermLine bob -- owner signature (bob's sent view)
cath <# "bob> contact address of @alice:"
void $ getTermLine cath -- link (signature stripped)
testShortLinkContactAddress :: HasCallStack => TestParams -> IO ()
testShortLinkContactAddress =
testChat4 aliceProfile bobProfile cathProfile danProfile $ \alice bob cath dan -> do