Skip to content

Commit

Permalink
Fail on MLS endpoints in galley when MLS is not enabled (#2899)
Browse files Browse the repository at this point in the history
* Fail on all MLS endpoints if MLS is not enabled

We are now treating the absence of a removal key as an indicator that
MLS is disabled on this backend, and throw a 400 error on all Galley
endpoints that have something to do with MLS.

* Add error return values to MLS fed endpoints

The federation endpoints `mls-welcome` and `on-mls-message-sent` will
now fail and return an error value when MLS is not enabled on the
receiving side.

* Check if MLS is disabled on other fed endpoints

* Adapt federation tests to API changes

* Test behaviour when MLS is disabled
  • Loading branch information
pcapriotti committed Dec 8, 2022
1 parent db33f1a commit 696f828
Show file tree
Hide file tree
Showing 18 changed files with 328 additions and 124 deletions.
1 change: 1 addition & 0 deletions changelog.d/1-api-changes/mls-enabled-galley
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fail early in galley when the MLS removal key is not configured
16 changes: 14 additions & 2 deletions libs/wire-api-federation/src/Wire/API/Federation/API/Galley.hs
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ type GalleyApi =
:<|> FedEndpoint "send-message" MessageSendRequest MessageSendResponse
:<|> FedEndpoint "on-user-deleted-conversations" UserDeletedConversationsNotification EmptyResponse
:<|> FedEndpoint "update-conversation" ConversationUpdateRequest ConversationUpdateResponse
:<|> FedEndpoint "mls-welcome" MLSWelcomeRequest EmptyResponse
:<|> FedEndpoint "on-mls-message-sent" RemoteMLSMessage EmptyResponse
:<|> FedEndpoint "mls-welcome" MLSWelcomeRequest MLSWelcomeResponse
:<|> FedEndpoint "on-mls-message-sent" RemoteMLSMessage RemoteMLSMessageResponse
:<|> FedEndpoint "send-mls-message" MessageSendRequest MLSMessageResponse
:<|> FedEndpoint "send-mls-commit-bundle" MessageSendRequest MLSMessageResponse
:<|> FedEndpoint "query-group-info" GetGroupInfoRequest GetGroupInfoResponse
Expand Down Expand Up @@ -244,6 +244,12 @@ data RemoteMLSMessage = RemoteMLSMessage
deriving (Arbitrary) via (GenericUniform RemoteMLSMessage)
deriving (ToJSON, FromJSON) via (CustomEncoded RemoteMLSMessage)

data RemoteMLSMessageResponse
= RemoteMLSMessageOk
| RemoteMLSMessageMLSNotEnabled
deriving stock (Eq, Show, Generic)
deriving (ToJSON, FromJSON) via (CustomEncoded RemoteMLSMessageResponse)

data MessageSendRequest = MessageSendRequest
{ -- | Conversation is assumed to be owned by the target domain, this allows
-- us to protect against relay attacks
Expand Down Expand Up @@ -316,6 +322,12 @@ newtype MLSWelcomeRequest = MLSWelcomeRequest
deriving (Arbitrary) via (GenericUniform MLSWelcomeRequest)
deriving (FromJSON, ToJSON) via (CustomEncoded MLSWelcomeRequest)

data MLSWelcomeResponse
= MLSWelcomeSent
| MLSWelcomeMLSNotEnabled
deriving stock (Eq, Generic, Show)
deriving (FromJSON, ToJSON) via (CustomEncoded MLSWelcomeResponse)

data MLSMessageResponse
= MLSMessageResponseError GalleyError
| MLSMessageResponseProtocolError Text
Expand Down
10 changes: 9 additions & 1 deletion libs/wire-api/src/Wire/API/Error/Galley.hs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ data GalleyError
| ConvNotFound
| ConvAccessDenied
| -- MLS Errors
MLSNonEmptyMemberList
MLSNotEnabled
| MLSNonEmptyMemberList
| MLSDuplicatePublicKey
| MLSKeyPackageRefNotFound
| MLSUnsupportedMessage
Expand Down Expand Up @@ -178,6 +179,13 @@ type instance MapError 'ConvNotFound = 'StaticError 404 "no-conversation" "Conve

type instance MapError 'ConvAccessDenied = 'StaticError 403 "access-denied" "Conversation access denied"

type instance
MapError 'MLSNotEnabled =
'StaticError
400
"mls-not-enabled"
"MLS is not configured on this backend. See docs.wire.com for instructions on how to enable it"

type instance MapError 'MLSNonEmptyMemberList = 'StaticError 400 "non-empty-member-list" "Attempting to add group members outside MLS"

type instance MapError 'MLSDuplicatePublicKey = 'StaticError 400 "mls-duplicate-public-key" "MLS public key for the given signature scheme already exists"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ type ConversationAPI =
( Summary "Get MLS group information"
:> CanThrow 'ConvNotFound
:> CanThrow 'MLSMissingGroupInfo
:> CanThrow 'MLSNotEnabled
:> ZLocalUser
:> "conversations"
:> QualifiedCapture "cnv" ConvId
Expand Down Expand Up @@ -321,6 +322,7 @@ type ConversationAPI =
:> Until 'V3
:> CanThrow 'ConvAccessDenied
:> CanThrow 'MLSNonEmptyMemberList
:> CanThrow 'MLSNotEnabled
:> CanThrow 'NotConnected
:> CanThrow 'NotATeamMember
:> CanThrow OperationDenied
Expand All @@ -338,6 +340,7 @@ type ConversationAPI =
:> From 'V3
:> CanThrow 'ConvAccessDenied
:> CanThrow 'MLSNonEmptyMemberList
:> CanThrow 'MLSNotEnabled
:> CanThrow 'NotConnected
:> CanThrow 'NotATeamMember
:> CanThrow OperationDenied
Expand Down Expand Up @@ -373,6 +376,7 @@ type ConversationAPI =
:> ZLocalUser
:> "conversations"
:> "mls-self"
:> CanThrow 'MLSNotEnabled
:> MultiVerb1
'GET
'[JSON]
Expand Down
5 changes: 5 additions & 0 deletions libs/wire-api/src/Wire/API/Routes/Public/Galley/MLS.hs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type MLSMessagingAPI =
"mls-welcome-message"
( Summary "Post an MLS welcome message"
:> CanThrow 'MLSKeyPackageRefNotFound
:> CanThrow 'MLSNotEnabled
:> "welcome"
:> ZConn
:> ReqBody '[MLS] (RawMLS Welcome)
Expand All @@ -54,6 +55,7 @@ type MLSMessagingAPI =
:> CanThrow 'MLSClientMismatch
:> CanThrow 'MLSCommitMissingReferences
:> CanThrow 'MLSKeyPackageRefNotFound
:> CanThrow 'MLSNotEnabled
:> CanThrow 'MLSProposalNotFound
:> CanThrow 'MLSProtocolErrorTag
:> CanThrow 'MLSSelfRemovalNotAllowed
Expand Down Expand Up @@ -83,6 +85,7 @@ type MLSMessagingAPI =
:> CanThrow 'MLSClientMismatch
:> CanThrow 'MLSCommitMissingReferences
:> CanThrow 'MLSKeyPackageRefNotFound
:> CanThrow 'MLSNotEnabled
:> CanThrow 'MLSProposalNotFound
:> CanThrow 'MLSProtocolErrorTag
:> CanThrow 'MLSSelfRemovalNotAllowed
Expand Down Expand Up @@ -112,6 +115,7 @@ type MLSMessagingAPI =
:> CanThrow 'MLSClientMismatch
:> CanThrow 'MLSCommitMissingReferences
:> CanThrow 'MLSKeyPackageRefNotFound
:> CanThrow 'MLSNotEnabled
:> CanThrow 'MLSProposalNotFound
:> CanThrow 'MLSProtocolErrorTag
:> CanThrow 'MLSSelfRemovalNotAllowed
Expand All @@ -134,6 +138,7 @@ type MLSMessagingAPI =
:<|> Named
"mls-public-keys"
( Summary "Get public keys used by the backend to sign external proposals"
:> CanThrow 'MLSNotEnabled
:> "public-keys"
:> MultiVerb1 'GET '[JSON] (Respond 200 "Public keys" MLSPublicKeys)
)
Expand Down
1 change: 1 addition & 0 deletions services/galley/galley.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ library
Galley.API.Mapping
Galley.API.Message
Galley.API.MLS
Galley.API.MLS.Enabled
Galley.API.MLS.GroupInfo
Galley.API.MLS.KeyPackage
Galley.API.MLS.Keys
Expand Down
5 changes: 4 additions & 1 deletion services/galley/src/Galley/API/Create.hs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import qualified Data.Set as Set
import Data.Time
import qualified Data.UUID.Tagged as U
import Galley.API.Error
import Galley.API.MLS
import Galley.API.MLS.KeyPackage (nullKeyPackageRef)
import Galley.API.MLS.Keys (getMLSRemovalKey)
import Galley.API.Mapping
Expand Down Expand Up @@ -93,6 +94,7 @@ createGroupConversation ::
ErrorS 'NotATeamMember,
ErrorS OperationDenied,
ErrorS 'NotConnected,
ErrorS 'MLSNotEnabled,
ErrorS 'MLSNonEmptyMemberList,
ErrorS 'MissingLegalholdConsent,
FederatorAccess,
Expand All @@ -117,8 +119,9 @@ createGroupConversation lusr conn newConv = do

case newConvProtocol newConv of
ProtocolMLSTag -> do
-- Here we fail early in order to notify users of this misconfiguration
assertMLSEnabled
unlessM (isJust <$> getMLSRemovalKey) $
-- We fail here to notify users early about this misconfiguration
throw (InternalErrorWithDescription "No backend removal key is configured (See 'mlsPrivateKeyPaths' in galley's config). Refusing to create MLS conversation.")
ProtocolProteusTag -> pure ()

Expand Down
144 changes: 79 additions & 65 deletions services/galley/src/Galley/API/Federation.hs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import qualified Data.Text.Lazy as LT
import Data.Time.Clock
import Galley.API.Action
import Galley.API.Error
import Galley.API.MLS.Enabled
import Galley.API.MLS.GroupInfo
import Galley.API.MLS.KeyPackage
import Galley.API.MLS.Message
Expand Down Expand Up @@ -138,11 +139,12 @@ onClientRemoved ::
Sem r EmptyResponse
onClientRemoved domain req = do
let qusr = Qualified (F.crrUser req) domain
for_ (F.crrConvs req) $ \convId -> do
mConv <- E.getConversation convId
for mConv $ \conv -> do
lconv <- qualifyLocal conv
removeClient lconv qusr (F.crrClient req)
whenM isMLSEnabled $ do
for_ (F.crrConvs req) $ \convId -> do
mConv <- E.getConversation convId
for mConv $ \conv -> do
lconv <- qualifyLocal conv
removeClient lconv qusr (F.crrClient req)
pure EmptyResponse

onConversationCreated ::
Expand Down Expand Up @@ -632,16 +634,17 @@ sendMLSCommitBundle remoteDomain msr =
. runError
. fmap (either (F.MLSMessageResponseProposalFailure . pfInner) id)
. runError
. mapToGalleyError @MLSBundleStaticErrors
$ do
assertMLSEnabled
loc <- qualifyLocal ()
let sender = toRemoteUnsafe remoteDomain (F.msrSender msr)
bundle <- either (throw . mlsProtocolError) pure $ deserializeCommitBundle (fromBase64ByteString (F.msrRawMessage msr))
mapToGalleyError @MLSBundleStaticErrors $ do
let msg = rmValue (cbCommitMsg bundle)
qcnv <- E.getConversationIdByGroupId (msgGroupId msg) >>= noteS @'ConvNotFound
when (qUnqualified qcnv /= F.msrConvId msr) $ throwS @'MLSGroupConversationMismatch
F.MLSMessageResponseUpdates . map lcuUpdate
<$> postMLSCommitBundle loc (qUntagged sender) Nothing qcnv Nothing bundle
let msg = rmValue (cbCommitMsg bundle)
qcnv <- E.getConversationIdByGroupId (msgGroupId msg) >>= noteS @'ConvNotFound
when (qUnqualified qcnv /= F.msrConvId msr) $ throwS @'MLSGroupConversationMismatch
F.MLSMessageResponseUpdates . map lcuUpdate
<$> postMLSCommitBundle loc (qUntagged sender) Nothing qcnv Nothing bundle

sendMLSMessage ::
( Members
Expand Down Expand Up @@ -675,17 +678,18 @@ sendMLSMessage remoteDomain msr =
. runError
. fmap (either (F.MLSMessageResponseProposalFailure . pfInner) id)
. runError
. mapToGalleyError @MLSMessageStaticErrors
$ do
assertMLSEnabled
loc <- qualifyLocal ()
let sender = toRemoteUnsafe remoteDomain (F.msrSender msr)
raw <- either (throw . mlsProtocolError) pure $ decodeMLS' (fromBase64ByteString (F.msrRawMessage msr))
mapToGalleyError @MLSMessageStaticErrors $ do
case rmValue raw of
SomeMessage _ msg -> do
qcnv <- E.getConversationIdByGroupId (msgGroupId msg) >>= noteS @'ConvNotFound
when (qUnqualified qcnv /= F.msrConvId msr) $ throwS @'MLSGroupConversationMismatch
F.MLSMessageResponseUpdates . map lcuUpdate
<$> postMLSMessage loc (qUntagged sender) Nothing qcnv Nothing raw
case rmValue raw of
SomeMessage _ msg -> do
qcnv <- E.getConversationIdByGroupId (msgGroupId msg) >>= noteS @'ConvNotFound
when (qUnqualified qcnv /= F.msrConvId msr) $ throwS @'MLSGroupConversationMismatch
F.MLSMessageResponseUpdates . map lcuUpdate
<$> postMLSMessage loc (qUntagged sender) Nothing qcnv Nothing raw

class ToGalleyRuntimeError (effs :: EffectRow) r where
mapToGalleyError ::
Expand Down Expand Up @@ -715,75 +719,84 @@ mlsSendWelcome ::
'[ BrigAccess,
Error InternalError,
GundeckAccess,
Input Env,
Input (Local ()),
Input UTCTime
]
r =>
Domain ->
F.MLSWelcomeRequest ->
Sem r EmptyResponse
mlsSendWelcome _origDomain (fromBase64ByteString . F.unMLSWelcomeRequest -> rawWelcome) = do
loc <- qualifyLocal ()
now <- input
welcome <- either (throw . InternalErrorWithDescription . LT.fromStrict) pure $ decodeMLS' rawWelcome
-- Extract only recipients local to this backend
rcpts <-
fmap catMaybes
$ traverse
( fmap (fmap cidQualifiedClient . hush)
. runError @(Tagged 'MLSKeyPackageRefNotFound ())
. derefKeyPackage
. gsNewMember
)
$ welSecrets welcome
let lrcpts = qualifyAs loc $ fst $ partitionQualified loc rcpts
sendLocalWelcomes Nothing now rawWelcome lrcpts
pure EmptyResponse
Sem r F.MLSWelcomeResponse
mlsSendWelcome _origDomain (fromBase64ByteString . F.unMLSWelcomeRequest -> rawWelcome) =
fmap (either (const MLSWelcomeMLSNotEnabled) (const MLSWelcomeSent))
. runError @(Tagged 'MLSNotEnabled ())
$ do
assertMLSEnabled
loc <- qualifyLocal ()
now <- input
welcome <- either (throw . InternalErrorWithDescription . LT.fromStrict) pure $ decodeMLS' rawWelcome
-- Extract only recipients local to this backend
rcpts <-
fmap catMaybes
$ traverse
( fmap (fmap cidQualifiedClient . hush)
. runError @(Tagged 'MLSKeyPackageRefNotFound ())
. derefKeyPackage
. gsNewMember
)
$ welSecrets welcome
let lrcpts = qualifyAs loc $ fst $ partitionQualified loc rcpts
sendLocalWelcomes Nothing now rawWelcome lrcpts

onMLSMessageSent ::
Members
'[ ExternalAccess,
GundeckAccess,
Input (Local ()),
Input Env,
MemberStore,
P.TinyLog
]
r =>
Domain ->
F.RemoteMLSMessage ->
Sem r EmptyResponse
onMLSMessageSent domain rmm = do
loc <- qualifyLocal ()
let rcnv = toRemoteUnsafe domain (F.rmmConversation rmm)
let users = Set.fromList (map fst (F.rmmRecipients rmm))
(members, allMembers) <-
first Set.fromList
<$> E.selectRemoteMembers (toList users) rcnv
unless allMembers $
P.warn $
Log.field "conversation" (toByteString' (tUnqualified rcnv))
Log.~~ Log.field "domain" (toByteString' (tDomain rcnv))
Log.~~ Log.msg
( "Attempt to send remote message to local\
\ users not in the conversation" ::
ByteString
)
let recipients = filter (\(u, _) -> Set.member u members) (F.rmmRecipients rmm)
-- FUTUREWORK: support local bots
let e =
Event (qUntagged rcnv) (F.rmmSender rmm) (F.rmmTime rmm) $
EdMLSMessage (fromBase64ByteString (F.rmmMessage rmm))
let mkPush :: (UserId, ClientId) -> MessagePush 'NormalMessage
mkPush uc = newMessagePush loc mempty Nothing (F.rmmMetadata rmm) uc e

runMessagePush loc (Just (qUntagged rcnv)) $
foldMap mkPush recipients
pure EmptyResponse
Sem r F.RemoteMLSMessageResponse
onMLSMessageSent domain rmm =
fmap (either (const RemoteMLSMessageMLSNotEnabled) (const RemoteMLSMessageOk))
. runError @(Tagged 'MLSNotEnabled ())
$ do
assertMLSEnabled
loc <- qualifyLocal ()
let rcnv = toRemoteUnsafe domain (F.rmmConversation rmm)
let users = Set.fromList (map fst (F.rmmRecipients rmm))
(members, allMembers) <-
first Set.fromList
<$> E.selectRemoteMembers (toList users) rcnv
unless allMembers $
P.warn $
Log.field "conversation" (toByteString' (tUnqualified rcnv))
Log.~~ Log.field "domain" (toByteString' (tDomain rcnv))
Log.~~ Log.msg
( "Attempt to send remote message to local\
\ users not in the conversation" ::
ByteString
)
let recipients = filter (\(u, _) -> Set.member u members) (F.rmmRecipients rmm)
-- FUTUREWORK: support local bots
let e =
Event (qUntagged rcnv) (F.rmmSender rmm) (F.rmmTime rmm) $
EdMLSMessage (fromBase64ByteString (F.rmmMessage rmm))
let mkPush :: (UserId, ClientId) -> MessagePush 'NormalMessage
mkPush uc = newMessagePush loc mempty Nothing (F.rmmMetadata rmm) uc e

runMessagePush loc (Just (qUntagged rcnv)) $
foldMap mkPush recipients

queryGroupInfo ::
( Members
'[ ConversationStore,
Input (Local ())
Input (Local ()),
Input Env
]
r,
Member MemberStore r
Expand All @@ -796,6 +809,7 @@ queryGroupInfo origDomain req =
. runError @GalleyError
. mapToGalleyError @MLSGroupInfoStaticErrors
$ do
assertMLSEnabled
lconvId <- qualifyLocal . ggireqConv $ req
let sender = toRemoteUnsafe origDomain . ggireqSender $ req
state <- getGroupInfoFromLocalConv (qUntagged sender) lconvId
Expand Down
Loading

0 comments on commit 696f828

Please sign in to comment.