Merge bddf787b01
into d6edcbd946
commit
e937a7fbea
@ -0,0 +1,559 @@
|
||||
# MSC3917: Cryptographically Constrained Room Membership
|
||||
|
||||
In the current Matrix protocol, room membership events are not
|
||||
cryptographically signed, except by homeservers during
|
||||
federation. This means that a malicious homeserver can easily insert
|
||||
additional members into an end-to-end encrypted room. The falsified
|
||||
members will not receive keys for past messages, since those are only
|
||||
shared by existing members when they invite new members, but the
|
||||
falsified members will still be provided with keys for all new
|
||||
messages. Although the new member joining the room will be visible to
|
||||
all of the existing members, making it more difficult to perform such
|
||||
an attack undetected, it would still be preferable to have a means for
|
||||
clients to independently verify that a member actually belongs in a
|
||||
room.
|
||||
|
||||
This proposal provides a method for clients to sign room membership
|
||||
events such that the room memberships form a tree of signatures rooted
|
||||
in the creation of the room, ensuring that every member belongs to a
|
||||
chain of invitations that ultimately leads back to the room's
|
||||
creator. This establishes a cryptographically verifiable bounding set
|
||||
of possible members of a room, significantly raising the barrier for
|
||||
homeservers to inject unauthorized members into the room.
|
||||
|
||||
In particular, this proposal provides the following security property:
|
||||
**A user will only be verified as a possible room member if they
|
||||
created the room, or if they joined the room with permission (explicit
|
||||
or implicit) from another verified possible room member. As long as
|
||||
state events are transmitted successfully, all such users will be
|
||||
verified as possible room members.** In this context, a user is
|
||||
defined as a Master Signing Key, and a set of devices with
|
||||
Self-Signing Key signatures rooted in that MSK. A room is defined as a
|
||||
Room Root Key, and a set of membership events with cryptographic
|
||||
signatures rooted in that RRK.
|
||||
|
||||
## Proposal
|
||||
|
||||
### Rooms as Keys
|
||||
|
||||
This proposal associates each room with a public signing key, which
|
||||
will be the root of a tree of signed state events related to user
|
||||
memberships. This key is called the Room Root Key, and is generated by
|
||||
the room's creator at creation.
|
||||
|
||||
In this proposal, the RRK will essentially become the cryptographic
|
||||
identity of a room - being a member of the room, and being able to
|
||||
verify others' membership in the room, requires knowing the RRK. Users
|
||||
who disagree about a room's RRK are, for all intents and purposes, not
|
||||
actually members of the same room. For this reason, we propose that
|
||||
the RRK should *be* the room ID. Similar to
|
||||
[MSC1228](https://github.com/matrix-org/matrix-spec-proposals/pull/1228),
|
||||
the new format of a room ID will be `![unpadded urlsafe-base64ed
|
||||
ed25519 public key]`,
|
||||
e.g. `!Sr_Vj3FIqyQ2WjJ9fWpUXRdz6fX4oFAjKrDmu198PnI`. However, note
|
||||
that unlike MSC1228, in this proposal the key is generated
|
||||
*client-side* by the room's creator, and not shared with the server.
|
||||
|
||||
### Membership Event Signature Tree
|
||||
|
||||
In what follows, we define a *cause-of-membership event* to be a
|
||||
`join` event or `m.room.create` event that made a user a purported
|
||||
member of a room.
|
||||
|
||||
This proposal has each user generate a new cryptographic signing key
|
||||
called the Room Signing Key, or RSK. The RSK is used for signing
|
||||
certain types of room state events that the user sends (specifically,
|
||||
invitations, joins, and join rule changes), so that other room members
|
||||
can verify that the events were genuinely sent by that user. The RSK
|
||||
should be signed by the Master Signing Key, and stored and retrieved
|
||||
alongside the user's other signing keys. This key will be identified
|
||||
by the string `m.cross_signing.room_signing`, and will be published to
|
||||
the `/keys/device_signing/upload` endpoint using the new optional
|
||||
field `room_signing_key`, with usage `["room_signing"]`.
|
||||
|
||||
The proposal also adds additional fields to several room state events,
|
||||
holding cryptographic signatures and related metadata:
|
||||
|
||||
- The `m.room.create` event should have the following new content
|
||||
fields:
|
||||
+ `room_root_key`: An Ed25519 public key generated for this specific
|
||||
room by the room creator, henceforth called the Room Root Key
|
||||
(RRK), that will serve as the root of the room membership
|
||||
signature tree.
|
||||
+ `creator_key`: The public part of the room creator's Master
|
||||
Signing Key.
|
||||
+ `signatures`: A signature of the event's `content` by the Room
|
||||
Root Key, generated using the normal process for signing JSON
|
||||
objects. For this purpose, the entity performing the signature is
|
||||
the room ID, and the key identifier is `"rrk"`.
|
||||
|
||||
Rooting room memberships in a Room Root Key, rather than directly in
|
||||
the creator's MSK, means that each room has a unique root key, and
|
||||
that as long as clients agree on the RRK, they will agree on the
|
||||
validity of the entire signature tree. Essentially, the RRK *is* the
|
||||
cryptographic identity of the room, just as an MSK is the
|
||||
cryptographic identity of a user.
|
||||
|
||||
<details>
|
||||
<summary>Example event</summary>
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "m.room.create",
|
||||
"sender": "@alice:example.com",
|
||||
"content": {
|
||||
"room_version": "9",
|
||||
"creator": "@alice:example.com",
|
||||
"room_root_key" : "/ZK6paR+wBkKcazPx2xijn/0g+m2KCRqdCUZ6agzaaE",
|
||||
"creator_key" : "D67j2Q4RixFBAikBWXb7NjokkRgTDVyeHyEHjl8Ib9",
|
||||
"signatures" : {
|
||||
"@alice:example.com" : {
|
||||
"ed25519:rrk" : "iI98hykGBn0MuLopSysQYY/6bSaxuSZL05yRI+20P51RtfL3mwEHxSm7x6B3TMvAauxXX5hwohk8rqiWBDBWCQ"
|
||||
}
|
||||
}
|
||||
},
|
||||
"state_key": "",
|
||||
...
|
||||
"event_id": "$OSorlEHbz-xyfIaoy200IxyJAI2oTdOYFubheGwNr7c",
|
||||
"room_id": "!_ZK6paR-wBkKcazPx2xijn_0g-m2KCRqdCUZ6agzaaE"
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
- The `m.room.member` event should have the following new content
|
||||
fields when the membership state is `invite` or `join`, *unless* it
|
||||
is an `invite` event created by the homeserver as a successor of an
|
||||
`m.room.third_party_invite` event:
|
||||
+ `sender_key`: The sender's public Room Signing Key, signed by
|
||||
their Master Signing Key, in the same `CrossSigningKey` format
|
||||
used by the [`/keys/device_signing/upload`
|
||||
endpoint](https://spec.matrix.org/v1.3/client-server-api/#post_matrixclientv3keysdevice_signingupload).
|
||||
This field is provided in order to simplify the process of
|
||||
connecting the sender's MSK to their RSK, particularly in cases
|
||||
where the sender may no longer be in the room or may have even
|
||||
deactivated their account.
|
||||
+ `parent_event_id`:
|
||||
* If this is an `invite` event sent directly by a user, the
|
||||
parent event is the inviter's cause-of-membership event.
|
||||
* If this is a `join` event, the parent event is the `invite`
|
||||
event or `m.room.join_rules` event that allowed this user to
|
||||
join.
|
||||
+ `user_key`: The public MSK of the user whose membership is being
|
||||
affected.
|
||||
+ `room_root_key`: The public RRK.
|
||||
+ `signatures`: A signature of this event's content by the sender's
|
||||
RSK, generated using the normal process for signing JSON objects.
|
||||
+ `unsigned`: If this is a `join` event for a restricted room based
|
||||
on membership in another room, and that other room has an RRK,
|
||||
then the unsigned data must include the following field:
|
||||
* `membership_events`: An array holding a chain of stripped state
|
||||
events proving the user's possible membership in the room
|
||||
specified in the join rule, starting with the
|
||||
cause-of-membership event, and following parent events back to
|
||||
the specified room's `m.room.create` event.
|
||||
|
||||
The RRK is included in the signed data as a way of ensuring that
|
||||
every new member agrees with the existing members on the true
|
||||
RRK. If a member does not know the true RRK, they may later be
|
||||
tricked into falsely believing that another user is a member of the
|
||||
room, and share keys for their own messages with that user.
|
||||
|
||||
If the `m.room.member` event *is* an `invite` event created as a
|
||||
successor of a third-party invite, it must instead include the
|
||||
following additional fields:
|
||||
+ `parent_event_id`: The ID of the `m.room.third_party_invite`
|
||||
event.
|
||||
+ `third_party_invite`:
|
||||
* `signed`:
|
||||
- `user_key`: The public MSK of the user being invited.
|
||||
|
||||
- The `m.room.join_rules` event should have the following new content
|
||||
fields when the join rule is `public` or `restricted`:
|
||||
+ `sender_key`: The sender's public Room Signing Key, signed by
|
||||
their Master Signing Key, in the same `CrossSigningKey` format
|
||||
used by the [`/keys/device_signing/upload`
|
||||
endpoint](https://spec.matrix.org/v1.3/client-server-api/#post_matrixclientv3keysdevice_signingupload).
|
||||
This field is provided in order to simplify the process of
|
||||
connecting the sender's MSK to their RSK, particularly in cases
|
||||
where the sender may no longer be in the room or may have even
|
||||
deactivated their account.
|
||||
+ `parent_event_id`: The ID of the sender's cause-of-membership
|
||||
event.
|
||||
+ `signatures`: A signature of this event by the sender's RSK,
|
||||
generated using the normal process for signing JSON objects.
|
||||
|
||||
- The `m.room.third_party_invite` event should have the following new
|
||||
content fields:
|
||||
+ `sender_key`: The sender's public Room Signing Key, signed by
|
||||
their Master Signing Key, in the same `CrossSigningKey` format
|
||||
used by the [`/keys/device_signing/upload`
|
||||
endpoint](https://spec.matrix.org/v1.3/client-server-api/#post_matrixclientv3keysdevice_signingupload).
|
||||
This field is provided in order to simplify the process of
|
||||
connecting the sender's MSK to their RSK, particularly in cases
|
||||
where the sender may no longer be in the room or may have even
|
||||
deactivated their account.
|
||||
+ `parent_event_id`: The ID of the sender's cause-of-membership
|
||||
event.
|
||||
+ `signatures`: A signature of this event's content by the sender's
|
||||
RSK, generated using the normal process for signing JSON objects.
|
||||
|
||||
Note that for these state events' content to be signed by clients, the
|
||||
relevant client-server API endpoints will need to be updated so that
|
||||
clients can submit complete signed event contents, rather than having
|
||||
the homeserver generate the events from scratch:
|
||||
|
||||
- The `creation_content` request field for the `/createRoom` endpoint
|
||||
will now hold all signed content fields of the `m.room.create`
|
||||
event. This includes the public part of the Room Root Key, and the
|
||||
server *must* use the RRK as the room ID as described above. The
|
||||
server may not modify any of the content fields, and may not add any
|
||||
additional content fields except for data under `unsigned`. If the
|
||||
event content provided is unacceptable for any reason, the server
|
||||
should reject the request with a suitable error.
|
||||
|
||||
- The body of a `/rooms/{roomId}/invite` request or of a `/join`
|
||||
request will now hold all signed content fields of the
|
||||
`m.room.member` event or `m.room.third_party_invite` event.
|
||||
|
||||
For third-party invitations in particular, the client must now be the
|
||||
one to communicate directly with the identity server and receive an
|
||||
MXID to invite directly, or a token to publish in the
|
||||
`m.room.third_party_invite` event. Furthermore, when the identity
|
||||
server creates an identity mapping, it must learn the invited user's
|
||||
public MSK, and include that in its signed data alongside the MXID and
|
||||
token.
|
||||
|
||||
With these fields in place, a user's cause-of-membership event can be
|
||||
cryptographically verified via the following procedure:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
start{{"What type of event<br/>is the cause-of-membership event?"}}
|
||||
|
||||
start ==>|<code>join</code> event|join_rrk{"Does the event<br/>contain the correct RRK<br/>and the user's MSK?"}
|
||||
join_rrk -->|No|reject
|
||||
join_rrk ==>|Yes|join_sig{"Does the event<br/>have a valid signature<br/>by the user's RSK?"}
|
||||
join_sig -->|No|reject
|
||||
join_sig ==>|Yes|join_rsk_msk{"Does the user's RSK<br/>have a valid signature<br/>by their MSK?"}
|
||||
join_rsk_msk -->|No|reject
|
||||
join_rsk_msk ==>|Yes|why_join{{"Look up<br/>the <code>join</code> event's<br/>parent event ID"}}
|
||||
|
||||
why_join ==>|<code>invite</code> event|invite_kind{{"How was<br/>the <code>invite</code> event<br/>created?"}}
|
||||
invite_kind ==>|Sent directly<br/>by a user|invite_rrk{"Does the event<br/>contain the correct RRK<br/>and the user's MSK?"}
|
||||
invite_rrk ---->|No|reject
|
||||
invite_rrk =====>|Yes|check_sender{"Does the event<br/>have a valid signature<br/>by the sender's RSK?"}
|
||||
check_sender -->|No|reject
|
||||
check_sender ==>|Yes|sender_rsk_msk{"Does the sender's RSK<br/>have a valid signature<br/>by their MSK?"}
|
||||
sender_rsk_msk ---->|No|reject
|
||||
sender_rsk_msk ==>|Yes|lookup_sender_cause["Lookup this event's<br/>parent event ID -<br/>i.e., the sender's<br/>cause-of-membership event"]
|
||||
lookup_sender_cause==>sender_recurse{"Does the sender's<br/>cause-of-membership event<br/>pass verification?"}
|
||||
sender_recurse -.-> start
|
||||
sender_recurse -->|No|reject
|
||||
sender_recurse =====>|Yes|accept
|
||||
invite_kind ==>|Created by the homeserver<br/>as a successor of an<br/><code>m.room.third_party_invite</code> event|idserver_sig{"Does the signed<br/>third-party-invite data<br/>have a valid signature<br/>from an identity server?"}
|
||||
idserver_sig -->|No|reject
|
||||
idserver_sig ==>|Yes|threepid_signed_msk{"Does the signed data<br/>contain the user's MSK?"}
|
||||
threepid_signed_msk -->|No|reject
|
||||
threepid_signed_msk ==>|Yes|lookup_threepid["Look up the parent<br/><code>m.room.third_party_invite</code> event"]
|
||||
lookup_threepid ==> threepid_token{"Does the event<br/>have a matching token,<br/>and include the<br/>identity server public key<br/>that made the signature?"}
|
||||
threepid_token -->|No|reject
|
||||
threepid_token ==>|Yes|check_sender
|
||||
|
||||
why_join ==>|<code>m.room.join_rules</code> event|join_rule_type{"What kind of<br/>join rule?"}
|
||||
join_rule_type==>|Public|check_sender
|
||||
join_rule_type ==>|Restricted|any_old_rooms{"Does the restricted<br/>join rule include any rooms<br/>whose IDs are not RRKs?"}
|
||||
any_old_rooms==>|Yes|check_sender
|
||||
any_old_rooms==>|No|join_state{"Based on<br/>the list of state events<br/>provided in the <code>join</code> event,<br/>does the user's cause-of-membership event<br/>at the start of the list<br/>pass verification?"}
|
||||
join_state -.-> start
|
||||
join_state ==>|Yes|joinrule_match{"Does the join rule event<br/>include a room ID that<br/>matches the room ID from<br/>the provided state?"}
|
||||
join_state ------>|No|reject
|
||||
joinrule_match ===>|Yes|check_sender
|
||||
joinrule_match -->|No|reject
|
||||
|
||||
start ==>|<code>m.room.create</code> event|create_rrk{"Does the event<br/>contain the correct RRK<br/>and the user's MSK?"}
|
||||
create_rrk -->|No|reject(["The user's cause-of-membership event<br/>does NOT pass verification"])
|
||||
create_rrk ==>|Yes|create_sig{"Does the event<br/>have a valid signature<br/>by the RRK?"}
|
||||
create_sig ------------->|No|reject
|
||||
create_sig ======>|Yes|accept(["The user's cause-of-membership<br/>event passes verification"])
|
||||
|
||||
style accept fill:#5f5
|
||||
style reject fill:#f66
|
||||
```
|
||||
|
||||
Note that this procedure specifically verifies that **a particular
|
||||
MSK** may legitimately belong in the room. Devices that claim to
|
||||
belong to a user, but are not signed by a Self-Signing Key signed by
|
||||
that particular MSK, must not be treated as belonging in the room.
|
||||
|
||||
If clients are unable to verify a user's cause-of-membership event for
|
||||
a room, they may refuse to share cryptographic material in that room
|
||||
with that user.
|
||||
|
||||
As verification of the entire membership event chain *requires*
|
||||
knowing the correct RRK for a room, it is critical that when joining a
|
||||
new room, clients receive its RRK in a way that cannot easily be
|
||||
falsified by the homeserver. In the case where a user is being
|
||||
directly invited by an existing contact, they will receive an
|
||||
`m.room.membership` invite event which contains the RRK and is signed
|
||||
by the inviter; therefore, as long as they have the correct MSK for
|
||||
their contact, they will have the correct RRK for the room. Simply
|
||||
put, the authenticity of the room is exactly as strong as the
|
||||
authenticity of communications with the inviter, which is an inherent
|
||||
limit on any chat system.
|
||||
|
||||
To address situations where users join a room without being directly
|
||||
invited, we make the following additional changes:
|
||||
|
||||
- The `m.space.child` event should have the following new content
|
||||
fields:
|
||||
+ `sender_key`: The sender's public Room Signing Key, signed by
|
||||
their Master Signing Key, in the same `CrossSigningKey` format
|
||||
used by the [`/keys/device_signing/upload`
|
||||
endpoint](https://spec.matrix.org/v1.3/client-server-api/#post_matrixclientv3keysdevice_signingupload).
|
||||
This field is provided in order to simplify the process of
|
||||
connecting the sender's MSK to their RSK, particularly in cases
|
||||
where the sender may no longer be in the room or may have even
|
||||
deactivated their account.
|
||||
+ `parent_event_id`: The ID of the sender's cause-of-membership
|
||||
event.
|
||||
+ `room_root_key`: The RRK of the child room. This is already
|
||||
provided by the child room's ID, which is the state key of the
|
||||
event; however, it is duplicated here so that it will be included
|
||||
in the signed event content.
|
||||
+ `signatures`: A signature of this event's content by the sender's
|
||||
RSK, generated using the normal process for signing JSON objects.
|
||||
|
||||
When checking the list of child rooms of a space, clients should
|
||||
verify that `m.space.child` events are properly signed, and that the
|
||||
senders' cause-of-membership events pass validation.
|
||||
|
||||
- `matrix:` URIs and `matrix.to` URIs for room aliases should have an
|
||||
additional query parameter `room_root_key`, holding the
|
||||
base64-encoded RRK of the room. The authenticity of RRKs obtained
|
||||
from these URIs is thus as strong as the authenticity of whatever
|
||||
communications channel the URIs were sent through, which is again a
|
||||
fundamental limit.
|
||||
|
||||
## Potential issues
|
||||
|
||||
The protocol outlined here is necessary, but not sufficient, to
|
||||
determine definitively whether a user is a room member. It provides a
|
||||
bounding set of cryptographically verifiable possible room members,
|
||||
defined by a tree of signed state events in which users authorize
|
||||
other users to join. It *does not* cryptographically verify
|
||||
that the senders of those state events had the required power levels
|
||||
at the time they issued the events, or that they didn't leave or get
|
||||
banned between joining and issuing the events.
|
||||
|
||||
Essentially, the primary goal of this proposal is to ensure that
|
||||
attackers (including malicious homeservers) lose their ability to
|
||||
*unilaterally* insert members into a room. In order to falsify room
|
||||
membership, a malicious homeserver must collude with or compromise a
|
||||
user who has been a legitimate member of the room at some point in
|
||||
time; or tamper with communications between some existing room member
|
||||
and a new member who they are inviting, and deceive the existing
|
||||
member about the new member's MSK before they are invited. In a world
|
||||
with ubiquitous TOFU via
|
||||
[MSC3834](https://github.com/matrix-org/matrix-spec-proposals/pull/3834),
|
||||
this deception must take place at the moment of first contact between
|
||||
the existing member and the future new member, and they must never
|
||||
attempt to verify each other out-of-band.
|
||||
|
||||
If a user resets their MSK for any reason, their membership in the
|
||||
room can no longer be verified. They will need to submit a new join
|
||||
event, possibly being re-invited or re-joining a room from a
|
||||
restricted join rule, before they can continue to be trusted as a room
|
||||
member. Again, this is a fundamental limitation: in a scenario where
|
||||
homeservers are untrusted, a user's MSK *must* be the root of their
|
||||
entire identity, and any change to the MSK requires manually
|
||||
re-establishing any and all trust that has been extended to that
|
||||
user. Ideally, this work should be coupled with work to provide users
|
||||
with more easy and reliable backup and recovery options, making MSK
|
||||
resets as rare as possible.
|
||||
|
||||
In order to participate in rooms belonging to the new room version in
|
||||
this proposal, it is a hard requirement that clients support this
|
||||
proposal at least well enough to add signatures and other required
|
||||
fields to the state events they send, even if they are not interested
|
||||
in verifying room membership themselves. It might be possible to allow
|
||||
older clients to participate, but forbid them from inviting new users
|
||||
or setting join rules; regardless, this presents an obstacle for
|
||||
smoothly rolling out this proposal in Matrix's ecosystem.
|
||||
|
||||
## Alternatives
|
||||
|
||||
To obtain stronger cryptographic guarantees regarding room membership,
|
||||
we could additionally mandate that *all* state events be signed
|
||||
client-side, and that clients be given all the information they need
|
||||
to perform their own state resolution without relying on the
|
||||
server. This would allow clients to confirm that invitations were
|
||||
issued by users with the correct power levels, among other
|
||||
things. However, in order to prevent users who have left (or been
|
||||
banned from) the room from colluding with the server to re-enter the
|
||||
room or invite others, a more complex strategy is required. Even with
|
||||
signatures on all state events, it is still easy for a server to
|
||||
simply delete the membership event in which a user leaves or is
|
||||
banned; likewise, the server can also delete state events where power
|
||||
levels are changed to reduce a user's capabilities.
|
||||
|
||||
To prevent the server from hiding state events, one option would be to
|
||||
not only sign state events, but to sign their causal relationships to
|
||||
other state events. Essentially, state events are a *directed acyclic
|
||||
graph*, with edges leading from a state event to various other state
|
||||
events that are causally prior to it. Every time a new state event is
|
||||
sent, it must be signed client-side by its sender (using RSKs in the
|
||||
same manner as above), and must include the full list of event IDs of
|
||||
*source* nodes (nodes with no incoming edges) in the currently-known
|
||||
state DAG - i.e., a minimal list of prior state event IDs sufficient to
|
||||
establish that the entire known state DAG is causally prior to the new
|
||||
state event.
|
||||
|
||||
Now, in order for the server to discard a state event, it must also
|
||||
discard *all* state events that are causally later than the discarded
|
||||
state event, in perpetuity; at a bare minimum, all state events sent
|
||||
by the sender of the discarded state event will need to be
|
||||
discarded. However, if state events are only sent infrequently, it may
|
||||
still be possible to perform this attack stealthily, without users
|
||||
noticing that one of their members has stopped sending any state
|
||||
events. For this reason, it would also be best to have all non-state
|
||||
events include the list of source node event IDs in the
|
||||
currently-known state DAG; in this case, rather than being signed by
|
||||
RSKs, these lists would be protected by the authenticity guarantees of
|
||||
the room's Megolm encryption. Now, in order to hide a single state
|
||||
event, the server must permanently *silence* the sender entirely
|
||||
within that room, as well as silencing any other users who have
|
||||
received the state event. Note that non-state events do not become
|
||||
*part of* the state DAG, and do not need to be linked-to in this way
|
||||
by later events.
|
||||
|
||||
Here is an example of the event history of a room with this
|
||||
alternative expanded proposal. In this diagram, every thick arrow
|
||||
represents a backreference from a new state event to an old state
|
||||
event, within the state event content signed by an RSK; every thin
|
||||
arrow represents a backreference from a non-state event to a state
|
||||
event, protected by Megolm's authenticity properties.
|
||||
|
||||
```mermaid
|
||||
flowchart BT
|
||||
create{{"<code>m.room.create</code><br/>(Alice creates the room)"}}
|
||||
member0{{"<code>m.room.member</code><br/>(Alice invites Bee)"}}==>create
|
||||
message0["<code>m.room.message</code><br/><b>Alice:</b> “Hi!”"]-->member0
|
||||
message1["<code>m.room.message</code><br/><b>Bee:</b> “hi Alice :)”"]--->member0
|
||||
powerlevel0{{"<code>m.room.power_levels</code><br/>(Alice makes Bee an admin)"}}====>member0
|
||||
member1{{"<code>m.room.member</code><br/>(Bee invites Caleb)"}}==>powerlevel0
|
||||
message2["<code>m.room.message</code><br/><b>Alice:</b> “Giving you admin,<br/>just please dont invite Caleb”"]-->powerlevel0
|
||||
powerlevel1{{"<code>m.room.power_levels</code><br/>(Bee makes Caleb moderator)"}}==>member1
|
||||
member2{{"<code>m.room.member</code><br/>(Alice bans Caleb)"}}==>member1
|
||||
member3{{"<code>m.room.member</code><br/>(Caleb invites Darrell)"}}==>powerlevel1
|
||||
message5["<code>m.room.message</code><br/><b>Darrell:</b> “hi guys”"]-->member3
|
||||
message3["<code>m.room.message</code><br/><b>Bee:</b> “agh sorry, I forgot<br/>you and Caleb dont get along!”"]-->member2
|
||||
message3-->powerlevel1
|
||||
member4{{"<code>m.room.member</code><br/>(Alice invites Eloise)"}}====>member3
|
||||
message4["<code>m.room.message</code><br/><b>Alice:</b> “It's okay, just please try<br/>to remember next time.”"]--->member2
|
||||
message4-->member3
|
||||
member4 =====>member2
|
||||
message6["<code>m.room.message</code><br/><b>Darrell:</b> “Wait am I still here?”"]-->member4
|
||||
message7["<code>m.room.message</code><br/><b>Bee:</b> “i have no idea,<br/>Eloise can you explain<br/>how state resolution<br/>works again?”"]--->member4
|
||||
|
||||
classDef ban fill:#ff6666;
|
||||
classDef causal fill:#ffbbbb
|
||||
class member2 ban;
|
||||
class message3,message4,member4,message6,message7 causal;
|
||||
```
|
||||
|
||||
Now, in order for a server to delete the event in which Alice bans
|
||||
Caleb (highlighted in red), it would also need to delete *all* events
|
||||
with edges leading to that event (highlighted in pink), or else
|
||||
clients would easily be able to tell that room history had been
|
||||
tampered with.
|
||||
|
||||
In order to fully make use of this information, clients would also
|
||||
need to be provided with all the information required to perform their
|
||||
own state resolution independently of homeservers. Mainly, this would
|
||||
be the `auth_events` and `origin_server_ts` fields of events in the
|
||||
Server-Server API. Ideally, these would be signed client-side as well;
|
||||
this is easy enough to do for `auth_events`, but ensuring that
|
||||
`origin_server_ts` cannot be falsified (by clients or by servers) in
|
||||
ways that would impact state resolution is a more complex problem. In
|
||||
general, this alternative proposal has the potential to dovetail or
|
||||
conflict with existing P2P Matrix work in complex ways, and would need
|
||||
to be considered carefully in that light.
|
||||
|
||||
## Threat modeling
|
||||
|
||||
Here, we present a list of possible attacks, and how well these are
|
||||
mitigated by this baseline proposal (of a membership signature tree);
|
||||
by the expanded alternative version, with a full signed state DAG and
|
||||
client-side state resolution; and by [Signal's system for managing
|
||||
group memberships](https://eprint.iacr.org/2019/1416.pdf).
|
||||
|
||||
- ✅: Mitigated
|
||||
- 🚧: Partially mitigated
|
||||
- ❌: Not mitigated
|
||||
|
||||
| Attack | Membership tree | State DAG | Signal |
|
||||
|---|---|---|---|
|
||||
| Homeserver admin inserts a new member | ✅ | ✅ | ✅ |
|
||||
| Homeserver admin re-inserts a banned member | ❌ | ✅ | ❌ |
|
||||
| Banned member colludes with homeserver to insert a new member | ❌ | 🚧(1) | ❌ |
|
||||
| Unprivileged member colludes with homeserver to insert a new member | ❌ | ✅(2) | ❌ |
|
||||
|
||||
|
||||
1. Dependent on whether timestamps can be falsified to manipulate the
|
||||
precedence of causally-unconnected events during state resolution.
|
||||
2. If the user had invitation privileges at some point in the past,
|
||||
this is only partially mitigated, as in the case of a banned member.
|
||||
|
||||
## Security considerations
|
||||
|
||||
If a room's join rule is ever set to `public`, or to `restricted`
|
||||
based on a room with an older version that does not implement this
|
||||
proposal, then all alleged room memberships will be accepted as
|
||||
legitimate as long as the member signs their own join event and knows
|
||||
the correct RRK. In the case of public rooms, this is not particularly
|
||||
an issue, as verifying the membership of a public room is largely
|
||||
meaningless to begin with. Allowing restricted rooms based on older
|
||||
rooms is necessary for backwards compatibility, but since the
|
||||
membership of these older rooms cannot be verified, there is no longer
|
||||
a means of verifying the membership of a new room. Therefore,
|
||||
particularly in the case of migrating from an old version to a new
|
||||
version, room administrators will need to manually re-invite all
|
||||
members they believe to be legitimate, rather than setting up a
|
||||
restricted join rule.
|
||||
|
||||
Third-party invites still inherently require trusting an identity
|
||||
server to sign identity mappings correctly. This proposal, by
|
||||
requiring that the inviter sign the `m.room.third_party_invite` event
|
||||
content, does provide the added protection that a legitimate room
|
||||
member must designate a list of trusted identity server public keys,
|
||||
rather than leaving the choice up to the homeserver. Short of manually
|
||||
verifying a user's third-party identity out-of-band and directly
|
||||
issuing an invite to their MXID, there is no real way to add further
|
||||
authenticity guarantees to this process.
|
||||
|
||||
## Unstable prefix
|
||||
|
||||
Newly-added field names will be prefixed with
|
||||
`org.matrix.msc3917.v1`. Test implementations can identify the RSK as
|
||||
`org.matrix.msc3917.v1.cross_signing.room_signing`, use the optional
|
||||
field `org.matrix.msc3917.v1.room_signing_key` for the
|
||||
`/keys/device_signing/upload` endpoint, and use the newly-added state
|
||||
event content fields (other than `signatures` and `unsigned`) prefixed
|
||||
by `org.matrix.msc3917.v1`.
|
||||
|
||||
The updated endpoints for creating, inviting, and joining rooms will
|
||||
all be prefixed with `/unstable/org.matrix.msc3917.v1/`.
|
||||
|
||||
## Dependencies
|
||||
|
||||
This MSC does not fundamentally require
|
||||
[MSC3834](https://github.com/matrix-org/matrix-spec-proposals/pull/3834/)
|
||||
(not yet accepted at the time of writing) in order to function, but
|
||||
the security guarantees offered by this proposal are much stronger
|
||||
when it is used in concert with TOFU.
|
||||
|
||||
The rooms-as-keys aspect of this proposal is modeled after
|
||||
[MSC1228](https://github.com/matrix-org/matrix-spec-proposals/pull/1228). However,
|
||||
this proposal conflicts with MSC1228 in that we require Room Root Keys
|
||||
to be generated client-side by room creators.
|
Loading…
Reference in New Issue