diff --git a/changelog.d/2-features/mls-stale-app-messages b/changelog.d/2-features/mls-stale-app-messages new file mode 100644 index 0000000000..5005ccbac9 --- /dev/null +++ b/changelog.d/2-features/mls-stale-app-messages @@ -0,0 +1 @@ +MLS application messages for older epochs are now rejected diff --git a/integration/test/Test/MLS.hs b/integration/test/Test/MLS.hs index 7cd2b466a1..5c53958c74 100644 --- a/integration/test/Test/MLS.hs +++ b/integration/test/Test/MLS.hs @@ -39,6 +39,33 @@ testSendMessageNoReturnToSender = do ) wsSender +testStaleApplicationMessage :: HasCallStack => Domain -> App () +testStaleApplicationMessage otherDomain = do + [alice, bob, charlie, dave, eve] <- + createAndConnectUsers [OwnDomain, otherDomain, OwnDomain, OwnDomain, OwnDomain] + [alice1, bob1, charlie1] <- traverse createMLSClient [alice, bob, charlie] + traverse_ uploadNewKeyPackage [bob1, charlie1] + void $ createNewGroup alice1 + + -- alice adds bob first + void $ createAddCommit alice1 [bob] >>= sendAndConsumeCommitBundle + + -- bob prepares some application messages + [msg1, msg2] <- replicateM 2 $ createApplicationMessage bob1 "hi alice" + + -- alice adds charlie and dave with different commits + void $ createAddCommit alice1 [charlie] >>= sendAndConsumeCommitBundle + void $ createAddCommit alice1 [dave] >>= sendAndConsumeCommitBundle + + -- bob's application messages still go through + void $ postMLSMessage bob1 msg1.message >>= getJSON 201 + + -- alice adds eve + void $ createAddCommit alice1 [eve] >>= sendAndConsumeCommitBundle + + -- bob's application messages are now rejected + void $ postMLSMessage bob1 msg2.message >>= getJSON 409 + testMixedProtocolUpgrade :: HasCallStack => Domain -> App () testMixedProtocolUpgrade secondDomain = do (alice, tid) <- createTeam OwnDomain diff --git a/services/galley/src/Galley/API/MLS/Message.hs b/services/galley/src/Galley/API/MLS/Message.hs index 3db183b2b2..d7c721a66b 100644 --- a/services/galley/src/Galley/API/MLS/Message.hs +++ b/services/galley/src/Galley/API/MLS/Message.hs @@ -371,6 +371,7 @@ postMLSMessageToLocalConv :: Sem r ([LocalConversationUpdate], Maybe UnreachableUsers) postMLSMessageToLocalConv qusr c con msg ctype convOrSubId = do lConvOrSub <- fetchConvOrSub qusr msg.groupId ctype convOrSubId + let convOrSub = tUnqualified lConvOrSub for_ msg.sender $ \sender -> void $ getSenderIdentity qusr c sender lConvOrSub @@ -380,12 +381,23 @@ postMLSMessageToLocalConv qusr c con msg ctype convOrSubId = do IncomingMessageContentPublic pub -> case pub.content of FramedContentCommit _commit -> throwS @'MLSUnsupportedMessage FramedContentApplicationData _ -> throwS @'MLSUnsupportedMessage + -- proposal message FramedContentProposal prop -> processProposal qusr lConvOrSub msg.groupId msg.epoch pub prop IncomingMessageContentPrivate -> do - when ((tUnqualified lConvOrSub).migrationState == MLSMigrationMixed) $ + -- application message: + + -- reject all application messages if the conv is in mixed state + when (convOrSub.migrationState == MLSMigrationMixed) $ throwS @'MLSUnsupportedMessage + -- reject application messages older than 2 epochs + let epochInt :: Epoch -> Integer + epochInt = fromIntegral . epochNumber + when + (epochInt msg.epoch < epochInt convOrSub.mlsMeta.cnvmlsEpoch - 2) + $ throwS @'MLSStaleMessage + unreachables <- propagateMessage qusr (Just c) lConvOrSub con msg.rawMessage (tUnqualified lConvOrSub).members pure ([], unreachables)