Skip to content

Commit

Permalink
Forward local MLS Welcome messages (#2175)
Browse files Browse the repository at this point in the history
* MLS welcome message endpoint stub

* Add RawMLS wrapper

This makes it easier to verify signatures and to forward raw serialised
MLS objects. See haddocks for more details.

* Add assertion for raw TBS key package

* Dereference key packages in welcome messages

* Extract 'MessagePush' and employ singletons

* Remove legacy `Event` models

* Send local welcome messages

* Update to most recent crypto-cli

* Patch crypto-cli to keep using MLS ciphersuite 1

For the moment, we are going to keep ciphersuite 1 as the only supported
ciphersuite. This commit patches crypto-cli (used in integration tests)
to reflect this choice.

* Make base64url encoding for url pieces unpadded

* Integration tests for welcome message propagation

* Remove Comonad instance for RawMLS

* Use time effect in sendWelcomes

* Add a specific event constructor for MLS welcomes

* Test MLS welcome event

* Add mls welcome endpoint to nginz chart

* Add CHANGELOG entry

* Add golden tests for new event types

* Use proper temporary directory in MLS test

* Add connection ID to MLS welcome endpoint
  • Loading branch information
pcapriotti authored Mar 25, 2022
1 parent c0954a3 commit 6cc70cd
Show file tree
Hide file tree
Showing 42 changed files with 3,622 additions and 532 deletions.
1 change: 1 addition & 0 deletions changelog.d/0-release-notes/mls-welcome
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Note for wire.com operators: deploy nginz
3 changes: 3 additions & 0 deletions changelog.d/2-features/mls
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
MLS implementation progress:

- welcome messages are now being propagated
3 changes: 3 additions & 0 deletions charts/nginz/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,9 @@ nginx_conf:
disable_zauth: true
envs:
- all
- path: /mls/welcome
envs:
- all
gundeck:
- path: /push
envs:
Expand Down
5 changes: 5 additions & 0 deletions deploy/services-demo/conf/nginz/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,11 @@ http {
proxy_pass http://galley;
}

location /mls/welcome {
include common_response_with_zauth.conf;
proxy_pass http://galley;
}

# Gundeck Endpoints

rewrite ^/api-docs/push /push/api-docs?base_url=http://127.0.0.1:8080/ break;
Expand Down
4 changes: 2 additions & 2 deletions libs/types-common/src/Data/Json/Util.hs
Original file line number Diff line number Diff line change
Expand Up @@ -197,10 +197,10 @@ instance ToSchema Base64ByteString where
schema = fromBase64ByteString .= fmap Base64ByteString base64SchemaN

instance FromHttpApiData Base64ByteString where
parseUrlPiece = bimap Text.pack Base64ByteString . B64U.decode . Text.encodeUtf8
parseUrlPiece = bimap Text.pack Base64ByteString . B64U.decodeUnpadded . Text.encodeUtf8

instance ToHttpApiData Base64ByteString where
toUrlPiece = Text.decodeUtf8With Text.lenientDecode . B64U.encode . fromBase64ByteString
toUrlPiece = Text.decodeUtf8With Text.lenientDecode . B64U.encodeUnpadded . fromBase64ByteString

instance S.ToParamSchema Base64ByteString where
toParamSchema _ = mempty & S.type_ ?~ S.SwaggerString
Expand Down
6 changes: 6 additions & 0 deletions libs/wire-api/src/Wire/API/ErrorDescription.hs
Original file line number Diff line number Diff line change
Expand Up @@ -423,3 +423,9 @@ type DuplicateMLSPublicKey =
400
"mls-duplicate-public-key"
"MLS public key for the given signature scheme already exists"

type UnknownWelcomeRecipient =
ErrorDescription
400
"mls-unknown-welcome-recipient"
"One of the key packages of a welcome message could not be mapped to a known client"
208 changes: 20 additions & 188 deletions libs/wire-api/src/Wire/API/Event/Conversation.hs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ module Wire.API.Event.Conversation
_EdConversation,
_EdTyping,
_EdOtrMessage,
_EdMLSMessage,
_EdMLSWelcome,

-- * Event data helpers
SimpleMember (..),
Expand All @@ -59,25 +61,6 @@ module Wire.API.Event.Conversation
Conversation (..),
TypingData (..),
QualifiedUserIdList (..),

-- * Swagger
modelEvent,
modelMemberEvent,
modelConnectEvent,
modelConversationReceiptModeUpdateEvent,
modelConversationNameUpdateEvent,
modelConversationAccessUpdateEvent,
modelConversationMessageTimerUpdateEvent,
modelConversationCodeUpdateEvent,
modelConversationCodeDeleteEvent,
modelMemberUpdateEvent,
modelTypingEvent,
modelOtrMessageEvent,
modelMembers,
modelConnect,
modelMemberUpdateData,
modelOtrMessage,
typeEventType,
)
where

Expand All @@ -88,20 +71,19 @@ import Data.Aeson (FromJSON (..), ToJSON (..))
import qualified Data.Aeson as A
import qualified Data.Aeson.KeyMap as KeyMap
import Data.Id
import Data.Json.Util (ToJSONObject (toJSONObject), UTCTimeMillis (fromUTCTimeMillis), toUTCTimeMillis)
import Data.Json.Util
import Data.Qualified
import Data.Schema
import qualified Data.Swagger as S
import qualified Data.Swagger.Build.Api as Doc
import Data.Time
import Imports
import qualified Test.QuickCheck as QC
import URI.ByteString ()
import Wire.API.Arbitrary (Arbitrary (arbitrary), GenericUniform (..))
import Wire.API.Conversation
import Wire.API.Conversation.Code (ConversationCode (..), modelConversationCode)
import Wire.API.Conversation.Code (ConversationCode (..))
import Wire.API.Conversation.Role
import Wire.API.Conversation.Typing (TypingData (..), modelTyping)
import Wire.API.Conversation.Typing (TypingData (..))
import Wire.API.User (QualifiedUserIdList (..))

--------------------------------------------------------------------------------
Expand All @@ -118,34 +100,6 @@ data Event = Event
evtType :: Event -> EventType
evtType = eventDataType . evtData

modelEvent :: Doc.Model
modelEvent = Doc.defineModel "Event" $ do
Doc.description "Event data"
Doc.property "type" typeEventType $
Doc.description "Event type"
Doc.property "conversation" Doc.bytes' $
Doc.description "Conversation ID"
Doc.property "from" Doc.bytes' $
Doc.description "User ID"
Doc.property "time" Doc.dateTime' $
Doc.description "Date and time this event occurred"
-- This doesn't really seem to work in swagger-ui.
-- The children/subTypes are not displayed.
Doc.children
"type"
[ modelMemberEvent,
modelConnectEvent,
modelConversationReceiptModeUpdateEvent,
modelConversationNameUpdateEvent,
modelConversationAccessUpdateEvent,
modelConversationMessageTimerUpdateEvent,
modelConversationCodeUpdateEvent,
modelConversationCodeDeleteEvent,
modelMemberUpdateEvent,
modelTypingEvent,
modelOtrMessageEvent
]

instance Arbitrary Event where
arbitrary = do
typ <- arbitrary
Expand All @@ -171,6 +125,8 @@ data EventType
| ConvDelete
| ConvReceiptModeUpdate
| OtrMessageAdd
| MLSMessageAdd
| MLSWelcome
| Typing
deriving stock (Eq, Show, Generic, Enum, Bounded)
deriving (Arbitrary) via (GenericUniform EventType)
Expand All @@ -193,29 +149,11 @@ instance ToSchema EventType where
element "conversation.delete" ConvDelete,
element "conversation.connect-request" ConvConnect,
element "conversation.typing" Typing,
element "conversation.otr-message-add" OtrMessageAdd
element "conversation.otr-message-add" OtrMessageAdd,
element "conversation.mls-message-add" MLSMessageAdd,
element "conversation.mls-welcome" MLSWelcome
]

typeEventType :: Doc.DataType
typeEventType =
Doc.string $
Doc.enum
[ "conversation.member-join",
"conversation.member-leave",
"conversation.member-update",
"conversation.rename",
"conversation.access-update",
"conversation.receipt-mode-update",
"conversation.message-timer-update",
"conversation.code-update",
"conversation.code-delete",
"conversation.create",
"conversation.delete",
"conversation.connect-request",
"conversation.typing",
"conversation.otr-message-add"
]

data EventData
= EdMembersJoin SimpleMembers
| EdMembersLeave QualifiedUserIdList
Expand All @@ -231,63 +169,10 @@ data EventData
| EdConversation Conversation
| EdTyping TypingData
| EdOtrMessage OtrMessage
| EdMLSMessage ByteString
| EdMLSWelcome ByteString
deriving stock (Eq, Show, Generic)

modelMemberEvent :: Doc.Model
modelMemberEvent = Doc.defineModel "MemberEvent" $ do
Doc.description "member event"
Doc.property "data" (Doc.ref modelMembers) $ Doc.description "members data"

modelConnectEvent :: Doc.Model
modelConnectEvent = Doc.defineModel "ConnectEvent" $ do
Doc.description "connect event"
Doc.property "data" (Doc.ref modelConnect) $ Doc.description "connect data"

modelConversationReceiptModeUpdateEvent :: Doc.Model
modelConversationReceiptModeUpdateEvent = Doc.defineModel "ConversationReceiptModeUpdateEvent" $ do
Doc.description "conversation receipt mode update event"
Doc.property "data" (Doc.ref modelConversationReceiptModeUpdate) $ Doc.description "conversation receipt mode data"

modelConversationNameUpdateEvent :: Doc.Model
modelConversationNameUpdateEvent = Doc.defineModel "ConversationNameUpdateEvent" $ do
Doc.description "conversation update event"
Doc.property "data" (Doc.ref modelConversationUpdateName) $ Doc.description "conversation name"

modelConversationAccessUpdateEvent :: Doc.Model
modelConversationAccessUpdateEvent = Doc.defineModel "ConversationAccessUpdateEvent" $ do
Doc.description "conversation access update event"
Doc.property "data" (Doc.ref modelConversationAccessData) $ Doc.description "conversation access data"

modelConversationMessageTimerUpdateEvent :: Doc.Model
modelConversationMessageTimerUpdateEvent = Doc.defineModel "ConversationMessageTimerUpdateEvent" $ do
Doc.description "conversation message timer update event"
Doc.property "data" (Doc.ref modelConversationMessageTimerUpdate) $ Doc.description "conversation message timer data"

modelConversationCodeUpdateEvent :: Doc.Model
modelConversationCodeUpdateEvent = Doc.defineModel "ConversationCodeUpdateEvent" $ do
Doc.description "conversation code update event"
Doc.property "data" (Doc.ref modelConversationCode) $ Doc.description "conversation code data"

modelConversationCodeDeleteEvent :: Doc.Model
modelConversationCodeDeleteEvent =
Doc.defineModel "ConversationCodeDeleteEvent" $
Doc.description "conversation code delete event"

modelMemberUpdateEvent :: Doc.Model
modelMemberUpdateEvent = Doc.defineModel "MemberUpdateEvent" $ do
Doc.description "member update event"
Doc.property "data" (Doc.ref modelMemberUpdateData) $ Doc.description "member data"

modelTypingEvent :: Doc.Model
modelTypingEvent = Doc.defineModel "TypingEvent" $ do
Doc.description "typing event"
Doc.property "data" (Doc.ref modelTyping) $ Doc.description "typing data"

modelOtrMessageEvent :: Doc.Model
modelOtrMessageEvent = Doc.defineModel "OtrMessage" $ do
Doc.description "off-the-record message event"
Doc.property "data" (Doc.ref modelOtrMessage) $ Doc.description "OTR message"

genEventData :: EventType -> QC.Gen EventData
genEventData = \case
MemberJoin -> EdMembersJoin <$> arbitrary
Expand All @@ -303,6 +188,8 @@ genEventData = \case
ConvReceiptModeUpdate -> EdConvReceiptModeUpdate <$> arbitrary
Typing -> EdTyping <$> arbitrary
OtrMessageAdd -> EdOtrMessage <$> arbitrary
MLSMessageAdd -> EdMLSMessage <$> arbitrary
MLSWelcome -> EdMLSWelcome <$> arbitrary
ConvDelete -> pure EdConvDelete

eventDataType :: EventData -> EventType
Expand All @@ -319,6 +206,8 @@ eventDataType (EdConversation _) = ConvCreate
eventDataType (EdConvReceiptModeUpdate _) = ConvReceiptModeUpdate
eventDataType (EdTyping _) = Typing
eventDataType (EdOtrMessage _) = OtrMessageAdd
eventDataType (EdMLSMessage _) = MLSMessageAdd
eventDataType (EdMLSWelcome _) = MLSWelcome
eventDataType EdConvDelete = ConvDelete

--------------------------------------------------------------------------------
Expand All @@ -344,13 +233,6 @@ instance ToSchema SimpleMembers where
(array schema)
)

-- | Used both for 'SimpleMembers' and 'UserIdList'.
modelMembers :: Doc.Model
modelMembers =
Doc.defineModel "Members" $
Doc.property "users" (Doc.unique $ Doc.array Doc.bytes') $
Doc.description "List of user IDs"

data SimpleMember = SimpleMember
{ smQualifiedId :: Qualified UserId,
smConvRoleName :: RoleName
Expand Down Expand Up @@ -396,19 +278,6 @@ connectObjectSchema =
<*> cName .= optField "name" (maybeWithDefault A.Null schema)
<*> cEmail .= optField "email" (maybeWithDefault A.Null schema)

modelConnect :: Doc.Model
modelConnect = Doc.defineModel "Connect" $ do
Doc.description "user to user connection request"
Doc.property "recipient" Doc.bytes' $
Doc.description "The user ID to connect to"
Doc.property "message" Doc.string' $
Doc.description "Initial message to send to user"
Doc.property "name" Doc.string' $
Doc.description "Name of requestor"
Doc.property "email" Doc.string' $ do
Doc.description "E-Mail of requestor"
Doc.optional

-- | Outbound member updates. When a user A acts upon a user B,
-- then a user event is generated where B's user ID is set
-- as misTarget.
Expand Down Expand Up @@ -445,31 +314,6 @@ memberUpdateDataObjectSchema =
<*> misHiddenRef .= maybe_ (optField "hidden_ref" schema)
<*> misConvRoleName .= maybe_ (optField "conversation_role" schema)

modelMemberUpdateData :: Doc.Model
modelMemberUpdateData = Doc.defineModel "MemberUpdateData" $ do
Doc.description "Event data on member updates"
Doc.property "target" Doc.bytes' $ do
Doc.description "Target ID of the user that the action was performed on"
Doc.optional
Doc.property "otr_muted_ref" Doc.bytes' $ do
Doc.description "A reference point for (un)muting"
Doc.optional
Doc.property "otr_archived" Doc.bool' $ do
Doc.description "Whether to notify on conversation updates"
Doc.optional
Doc.property "otr_archived_ref" Doc.bytes' $ do
Doc.description "A reference point for (un)archiving"
Doc.optional
Doc.property "hidden" Doc.bool' $ do
Doc.description "Whether the conversation is hidden"
Doc.optional
Doc.property "hidden_ref" Doc.bytes' $ do
Doc.description "A reference point for (un)hiding"
Doc.optional
Doc.property "conversation_role" Doc.string' $ do
Doc.description "Name of the conversation role to update to"
Doc.optional

data AddCodeResult
= CodeAdded Event
| CodeAlreadyExisted ConversationCode
Expand Down Expand Up @@ -514,21 +358,6 @@ otrMessageObjectSchema =
"Extra (symmetric) data (i.e. ciphertext, Base64 in JSON) \
\that is common with all other recipients."

modelOtrMessage :: Doc.Model
modelOtrMessage = Doc.defineModel "OtrMessage" $ do
Doc.description "Encrypted message of a conversation"
Doc.property "sender" Doc.bytes' $
Doc.description "The sender's client ID"
Doc.property "recipient" Doc.bytes' $
Doc.description "The recipient's client ID"
Doc.property "text" Doc.bytes' $
Doc.description "The ciphertext for the recipient (Base64 in JSON)"
Doc.property "data" Doc.bytes' $ do
Doc.description
"Extra (symmetric) data (i.e. ciphertext, Base64 in JSON) \
\that is common with all other recipients."
Doc.optional

makePrisms ''EventData

taggedEventDataSchema :: ObjectSchema SwaggerDoc (EventType, EventData)
Expand All @@ -549,6 +378,8 @@ taggedEventDataSchema =
ConvMessageTimerUpdate -> tag _EdConvMessageTimerUpdate (unnamed schema)
ConvReceiptModeUpdate -> tag _EdConvReceiptModeUpdate (unnamed schema)
OtrMessageAdd -> tag _EdOtrMessage (unnamed schema)
MLSMessageAdd -> tag _EdMLSMessage base64Schema
MLSWelcome -> tag _EdMLSWelcome base64Schema
Typing -> tag _EdTyping (unnamed schema)
ConvCodeDelete -> tag _EdConvCodeDelete null_
ConvDelete -> tag _EdConvDelete null_
Expand All @@ -569,10 +400,11 @@ eventObjectSchema =
mk (_, d) cid uid tm = Event cid uid tm d

instance ToJSONObject Event where
toJSONObject =
toJSONObject e =
KeyMap.fromList
. fromMaybe []
. schemaOut eventObjectSchema
$ e

instance FromJSON Event where
parseJSON = schemaParseJSON
Expand Down
5 changes: 3 additions & 2 deletions libs/wire-api/src/Wire/API/MLS/Credential.hs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ data ClientIdentity = ClientIdentity
deriving stock (Eq, Show, Generic)
deriving (FromJSON, ToJSON, S.ToSchema) via Schema ClientIdentity

cidQualifiedClient :: ClientIdentity -> Qualified (UserId, ClientId)
cidQualifiedClient cid = Qualified (ciUser cid, ciClient cid) (ciDomain cid)

instance ToSchema ClientIdentity where
schema =
object "ClientIdentity" $
Expand All @@ -146,5 +149,3 @@ instance ParseMLS ClientIdentity where

mkClientIdentity :: Qualified UserId -> ClientId -> ClientIdentity
mkClientIdentity (Qualified uid domain) cid = ClientIdentity domain uid cid

instance Binary ClientIdentity
Loading

0 comments on commit 6cc70cd

Please sign in to comment.