diff --git a/libs/wire-api/src/Wire/API/MLS/AuthenticatedContent.hs b/libs/wire-api/src/Wire/API/MLS/AuthenticatedContent.hs index 5daf745bfb5..394a18ede1a 100644 --- a/libs/wire-api/src/Wire/API/MLS/AuthenticatedContent.hs +++ b/libs/wire-api/src/Wire/API/MLS/AuthenticatedContent.hs @@ -17,6 +17,7 @@ module Wire.API.MLS.AuthenticatedContent ( AuthenticatedContent (..), + TaggedSender (..), authContentRef, publicMessageRef, mkSignedPublicMessage, @@ -29,6 +30,7 @@ import Wire.API.MLS.CipherSuite import Wire.API.MLS.Context import Wire.API.MLS.Epoch import Wire.API.MLS.Group +import Wire.API.MLS.LeafNode import Wire.API.MLS.Message import Wire.API.MLS.Proposal import Wire.API.MLS.ProtocolVersion @@ -64,16 +66,33 @@ authContentRef cs = ProposalRef . csHash cs proposalContext . mkRawMLS publicMessageRef :: CipherSuiteTag -> PublicMessage -> ProposalRef publicMessageRef cs = authContentRef cs . msgAuthContent +-- | Sender, plus with a membership tag in the case of a member sender. +data TaggedSender + = TaggedSenderMember LeafIndex ByteString + | TaggedSenderExternal Word32 + | TaggedSenderNewMemberProposal + | TaggedSenderNewMemberCommit + +taggedSenderToSender :: TaggedSender -> Sender +taggedSenderToSender (TaggedSenderMember i _) = SenderMember i +taggedSenderToSender (TaggedSenderExternal n) = SenderExternal n +taggedSenderToSender TaggedSenderNewMemberProposal = SenderNewMemberProposal +taggedSenderToSender TaggedSenderNewMemberCommit = SenderNewMemberCommit + +taggedSenderMembershipTag :: TaggedSender -> Maybe ByteString +taggedSenderMembershipTag (TaggedSenderMember _ t) = Just t +taggedSenderMembershipTag _ = Nothing + -- | Craft a message with the backend itself as a sender. Return the message and its ref. mkSignedPublicMessage :: - SecretKey -> PublicKey -> GroupId -> Epoch -> FramedContentData -> PublicMessage -mkSignedPublicMessage priv pub gid epoch payload = + SecretKey -> PublicKey -> GroupId -> Epoch -> TaggedSender -> FramedContentData -> PublicMessage +mkSignedPublicMessage priv pub gid epoch sender payload = let framedContent = mkRawMLS FramedContent { groupId = gid, epoch = epoch, - sender = SenderExternal 0, + sender = taggedSenderToSender sender, content = payload, authenticatedData = mempty } @@ -88,5 +107,5 @@ mkSignedPublicMessage priv pub gid epoch payload = in PublicMessage { content = framedContent, authData = mkRawMLS (FramedContentAuthData sig Nothing), - membershipTag = Nothing + membershipTag = taggedSenderMembershipTag sender } diff --git a/libs/wire-api/test/unit/Test/Wire/API/MLS.hs b/libs/wire-api/test/unit/Test/Wire/API/MLS.hs index 6842012fee1..c497d8c7a6e 100644 --- a/libs/wire-api/test/unit/Test/Wire/API/MLS.hs +++ b/libs/wire-api/test/unit/Test/Wire/API/MLS.hs @@ -150,6 +150,7 @@ testRemoveProposalMessageSignature = withSystemTempDirectory "mls" $ \tmp -> do publicKey gid (Epoch 1) + (TaggedSenderExternal 0) (FramedContentProposal proposal) message = mkMessage $ MessagePublic pmessage messageFilename = "signed-message.mls" diff --git a/services/galley/src/Galley/API/MLS/Message.hs b/services/galley/src/Galley/API/MLS/Message.hs index 4f2ada95fda..60a08b1c17b 100644 --- a/services/galley/src/Galley/API/MLS/Message.hs +++ b/services/galley/src/Galley/API/MLS/Message.hs @@ -75,7 +75,7 @@ import Wire.API.MLS.SubConversation -- [ ] restore deleted MLS unit tests -- [x] pass groupId and epoch to processProposal instead of the whole IncomingMessage -- [x] remove LWT in planMLSClientRemoval --- [ ] restore unsupported proposal integration test +-- [x] restore unsupported proposal integration test -- FUTUREWORK -- - Check that the capabilities of a leaf node in an add proposal contains all diff --git a/services/galley/src/Galley/API/MLS/Removal.hs b/services/galley/src/Galley/API/MLS/Removal.hs index 2a7e977dfda..f801bf06b5b 100644 --- a/services/galley/src/Galley/API/MLS/Removal.hs +++ b/services/galley/src/Galley/API/MLS/Removal.hs @@ -88,6 +88,7 @@ createAndSendRemoveProposals lConvOrSubConv indices qusr cm = do pubKey (cnvmlsGroupId meta) (cnvmlsEpoch meta) + (TaggedSenderExternal 0) (FramedContentProposal proposal) msg = mkRawMLS (mkMessage (MessagePublic pmsg)) storeProposal diff --git a/services/galley/test/integration/API/MLS.hs b/services/galley/test/integration/API/MLS.hs index d2b9991aa8f..83dc1947c82 100644 --- a/services/galley/test/integration/API/MLS.hs +++ b/services/galley/test/integration/API/MLS.hs @@ -29,6 +29,8 @@ import Control.Exception (throw) import Control.Lens (view) import Control.Lens.Extras import qualified Control.Monad.State as State +import Crypto.Error +import qualified Crypto.PubKey.Ed25519 as Ed25519 import qualified Data.Aeson as Aeson import Data.Domain import Data.Id @@ -60,10 +62,12 @@ import Wire.API.Error.Galley import Wire.API.Event.Conversation import Wire.API.Federation.API.Common import Wire.API.Federation.API.Galley +import Wire.API.MLS.AuthenticatedContent import Wire.API.MLS.CipherSuite import Wire.API.MLS.Credential import Wire.API.MLS.Keys import Wire.API.MLS.Message +import Wire.API.MLS.Proposal import Wire.API.MLS.Serialisation import Wire.API.MLS.SubConversation import Wire.API.Message @@ -1578,8 +1582,37 @@ testPublicKeys = do ) @?= [Ed25519] +--- | The test manually reads from mls-test-cli's store and extracts a private +--- key. The key is needed for signing an unsupported proposal, which is then +-- forwarded by the backend without being inspected. propUnsupported :: TestM () -propUnsupported = pure () -- TODO (app ack does not exist anymore) +propUnsupported = do + users@[_alice, bob] <- createAndConnectUsers (replicate 2 Nothing) + runMLSTest $ do + [alice1, bob1] <- traverse createMLSClient users + void $ uploadNewKeyPackage bob1 + (gid, _) <- setupMLSGroup alice1 + void $ createAddCommit alice1 [bob] >>= sendAndConsumeCommitBundle + + (priv, pub) <- clientKeyPair alice1 + pmsg <- + liftIO . throwCryptoErrorIO $ + mkSignedPublicMessage + <$> Ed25519.secretKey priv + <*> Ed25519.publicKey pub + <*> pure gid + <*> pure (Epoch 1) + <*> pure (TaggedSenderMember 0 "foo") + <*> pure + ( FramedContentProposal + (mkRawMLS (GroupContextExtensionsProposal [])) + ) + + let msg = mkMessage (MessagePublic pmsg) + let msgData = encodeMLS' msg + + -- we cannot consume this message, because the membership tag is fake + postMessage alice1 msgData !!! const 201 === statusCode testBackendRemoveProposalRecreateClient :: TestM () testBackendRemoveProposalRecreateClient = do diff --git a/services/galley/test/integration/API/MLS/Util.hs b/services/galley/test/integration/API/MLS/Util.hs index 82229ced9bc..ea2f3483f3a 100644 --- a/services/galley/test/integration/API/MLS/Util.hs +++ b/services/galley/test/integration/API/MLS/Util.hs @@ -35,6 +35,7 @@ import Control.Monad.Trans.Maybe import Data.Aeson.Lens import Data.Bifunctor import Data.Binary.Builder (toLazyByteString) +import Data.Binary.Get import qualified Data.ByteArray as BA import qualified Data.ByteString as BS import qualified Data.ByteString.Base64.URL as B64U @@ -239,7 +240,8 @@ liftTest = MLSTest . lift runMLSTest :: MLSTest a -> TestM a runMLSTest (MLSTest m) = - withSystemTempDirectory "mls" $ \tmp -> do + withSystemTempDirectory "mls" $ \_tmp -> do + let tmp = "/tmp/mls" saveRemovalKey (tmp "removal.key") evalStateT m @@ -944,11 +946,11 @@ clientKeyPair cid = do credential <- liftIO . BS.readFile $ bd cid2Str cid "store" T.unpack (T.decodeUtf8 (B64U.encode "self")) - let s = - credential ^.. key "signature_private_key" . key "value" . _Array . traverse . _Integer - & fmap fromIntegral - & BS.pack - pure $ BS.splitAt 32 s + case runGetOrFail + ((,) <$> parseMLSBytes @VarInt <*> parseMLSBytes @VarInt) + (LBS.fromStrict credential) of + Left (_, _, msg) -> liftIO $ assertFailure msg + Right (_, _, keys) -> pure keys receiveNewRemoteConv :: (MonadReader TestSetup m, MonadIO m) =>