Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MSC4047: Send Keys #4047

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
310 changes: 310 additions & 0 deletions proposals/4047-room-send-keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,310 @@
# MSC4047: Send Keys

A common feature request is a room structure where most/all members are not aware of each other. Matrix
currently requires all senders to be aware of each other for routing purposes, making it difficult to hide
a user's subscription to the room.
Comment on lines +3 to +5
Copy link

@andrewzhurov andrewzhurov May 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The direction of thought is interesting here.
Will I make stretch by extending it to "have rooms as topics anyone can chat about"?
Akin to adding tags to messages.

Interestingly, this unlocks one to define what he wants to see about a topic.
E.g., events about "politics" (tag) from my friends.

A kind of personalized feed.
That includes events shared to you by others cordially (since they know you need them). The grassroots style. Wondered about it here.


The core principle of this problem is tracked as [issue #367](https://github.com/matrix-org/matrix-spec/issues/367), though the exact semantics described by that issue are different from this proposal. Instead of limiting
membership visibility by power level, this MSC aims to introduce an ability for "external senders", with
an assumption that [MSC2753-style](https://github.com/matrix-org/matrix-spec-proposals/pull/2753) peeking
or other similar mechanism can be used to subscribe to the room without becoming a member.

External senders are accomplished with a "send key" in this proposal. A public key is published to the room
for event auth concerns, and a private key is sent to the desired senders. Anyone with that private key can
then send events into the room.

Dependencies:
* [MSC4046](https://github.com/matrix-org/matrix-spec-proposals/pull/4046)

## Proposal

*This MSC is done entirely in context of a future room version, due to event auth and redaction algorithm
changes.*

A simple public/private key pair is used as an additional authentication mechanism for events in a room.
The public portion of that key pair is persisted to the room as `m.room.send_key`, and the private
portion is shared out of band from the room. `m.room.send_key` MUST have an empty string as a `state_key`,
but can contain multiple keys under `content`. The use of a single event type is to avoid complications
with both event authorization and state resolution[^1].

**TODO**: Decide on default key pair type. Ed25519 keys would probably be fine?

An `m.room.send_key` looks as such:

```jsonc
{
// irrelevant fields not shown
"type": "m.room.send_key",
"state_key": "",
"content": {
"ed25519:efgh": "<unpadded base64 encoded public key>"
}
}
```

`m.room.send_key` retains all `content` upon redaction, as otherwise event authorization would fail when
the key is used. Invalidating keys is covered later in this proposal.

A state event naturally inhibits who is able to actually (re)generate a send key. It is suggested that
servers update their default power level templates for new rooms to include the `m.room.send_key` state
event as only available to "room admins", or at least the same as `m.room.join_rules`.

Events using a send key are signed by that send key:

```jsonc
{
// irrelevant fields not shown
"type": "m.room.message",
"sender": "@alice:example.org", // presumed to not be in the room at the time
"content": {
"msgtype": "m.text",
"body": "Hi"
},
"signatures": {
"example.org": {
"ed25519:abcd": "<sender signature, as required by auth rules>"
},
"$sendKeyEventId": {
"ed25519:efgh": "<signature with send key>"
}
}
}
```

This implies 3 things:

1. The sender has access to the private key.
2. The sender knows the event ID of the `m.room.send_key` event.
3. The sender is capable of signing a fully-formed PDU to prevent forgery attempts (see "Security
Considerations" section)

The first and second items are covered by the "Distribution of send keys" section in this proposal,
and the last item is covered by the new `/make` and `/send_pdu` APIs introduced later.

With respect to [event authorization](https://spec.matrix.org/v1.7/rooms/v10/#authorization-rules),
events use a send key when their `signatures` include a key starting with `$`. That key MUST appear
as in the event's `auth_events` and MUST be a state event of type `m.room.send_key`, with empty state
key. If the event is not referenced or does not point to the send key state event the event is rejected.
If the referenced send key state event does not contain the key ID being used, or the signature fails
verification, the event is rejected.

Note that as part of [receiving a PDU](https://spec.matrix.org/v1.7/server-server-api/#checks-performed-on-receipt-of-a-pdu)
the server already checks 3 conditions:

1. The event passes based upon its `auth_events`.
2. The event passes based upon the state prior to its own change (ie: a send key state event is not
sent using a new key only it contains).
3. The event passes based upon the current state of the room, which may be different from #1 and #2,
otherwise it is [soft-failed](https://spec.matrix.org/v1.7/server-server-api/#soft-failure).

The first two conditions are covered above where we say an event using a send key *must* reference a
valid send key, and have a valid accompanying signature. The third condition we cover with an additional
note to say that if the referenced key ID (regardless of event ID) fails the signature verification
using the current `m.room.send_key` state event then the event is soft-failed. This could be because
the key has been removed from the state event or the key itself changed.

Note that there are no rules to prevent a key from being regenerated. Preventing in-place changes to
keys is trivial with authorization rules, however there's not sufficient reason to actually prevent
the behaviour. Whether changing a key in-place or removing the old key before adding a new key, the
new `m.room.send_key` state event becomes current and causes use of the previous key to be soft-failed.

**OPEN QUESTION**: How interested are we in guaranteed history? If a bunch of events are sent using a
send key and that key is later regenerated/removed, a new server joining the room would ultimately
soft-fail all of those events. An "easy" fix to this would be to never allow send keys to change or
be removed, but then there's a gaping security hole in the room when the private key gets leaked.

The event authorization changes are covered more verbosely later in this proposal, in addition to a
description of how power levels work with respect to sent events.

### Distribution of send keys

After a user has generated a key and populated a relevant `m.room.send_key`, they need to actually
send the private key to an external sender (or several). This is done over encrypted
[to-device messages](https://spec.matrix.org/v1.7/client-server-api/#send-to-device-messaging),
shielding the private key material from anyone along the send path. The following `m.send_key` object
is encrypted using Olm before being sent, exactly like [`m.room_key`](https://spec.matrix.org/v1.7/client-server-api/#mroom_key).

```jsonc
{
"type": "m.send_key",
"content": {
"room_id": "!room:example.org", // room ID where the `m.room.send_key` event is
"event_id": "$eventIdOfSendKeyInRoom", // `m.room.send_key` event ID
"keys": { // the subset of keys the receiver is getting
"ed25519:efgh": "<unpadded base64 private key>"
},
"servers": [ // resident servers which can be useful for sending an event later
"example.org"
]
}
}
```

It is up to the sender to determine which of the receiver's devices will ultimately get the keys, and which
ones. Typically, it's expected that Olm sessions will be established with all of a target user's devices and
keys will be sent to all of them. Note that a receiver can trivially gossip the keys to its other devices if
it so chooses.

The receiver is then responsible for persisting the send key(s) and associated information. Secure storage
is recommended.

### Using send keys

Private keys are generally going to be kept within a client's secure storage, which is a bit of a problem
if the server needs to use that private key to send an event on its behalf. This MSC relies on the endpoints
described by [MSC4046](https://github.com/matrix-org/matrix-spec-proposals/pull/4046) to send PDUs into rooms.

Between the `GET /make_pdu` and `PUT /send_pdu` calls, the client would use the private key to sign the PDU
it constructed, making it legal to send into the room.

### Event authorization changes

*This section comprehensively covers the changes discussed in the MSC, duplicating details as needed.*

The following definitions are referenced by this section:

* [Authorization rules](https://spec.matrix.org/v1.7/rooms/v10/#authorization-rules)
* [Auth events selection algorithm](https://spec.matrix.org/v1.7/server-server-api#auth-events-selection)
* ["Checks performed upon receipt of a PDU"](https://spec.matrix.org/v1.7/server-server-api/#checks-performed-on-receipt-of-a-pdu)

**TODO**: Update for room version 11.

Note that throughout the changes described below there may be more implementation efficient methods. Specifically
in Change 3 a server can likely optimize the new rules down a bit, but for specification purposes we need to
describe the complete machine operation.

**Change 1**: Prevent `signatures` from containing not-`m.room.send_key` event IDs, and ensure Rule 2.2 passes
when `m.room.send_key` is referenced. This change also confirms that the send key signatures are valid. A
desirable characteristic of this change is that both multiple send keys and send key events can be used to
authorize an event - a failure in any one of those signatures causes the whole event to become rejected.

The auth events selection algorithm is appended with:

> * The current `m.room.send_key` event, if used.

The following new rules after Rule 2.4 are appended under Rule 2 of the authorization rules:

> 2. [...]
> 5. If entries matching each of the keys under `signatures` prefixed with `$` are not present, reject.
> 6. If any of the entries from 2.5 do not reference `m.room.send_key` with empty string state key, reject.

The text "Events must be signed by the server denoted by the `sender` property." is appended with:

> If events have "send key" signatures (Rules 2.5 and 2.6), all of those signatures must be valid per the
> `m.room.send_key` events they reference.

**Change 2**: Ensure events referencing old/regenerated send keys are soft-failed.

Check 6 of "checks performed upon receipt of a PDU" is clarified as such:

> With respect to Check 6, events using "send key" signatures must have a valid signature when using the current
> `m.room.send_key` state event in the room, otherwise it is "soft failed".
>
> For example, if the current `m.room.send_key` event ID is `$current`, and the following signature is present:
> ```json
> {
turt2live marked this conversation as resolved.
Show resolved Hide resolved
> "$event": {
> "ed25519:abcd": "<signature>"
> }
> }
> ```
>
> then the signature for `ed25519:abcd` must be valid when compared against `m.room.send_key`.

The signature validation is expected to be defined as part of the `m.room.send_key` event. Namely, for a given
key ID:

1. If `content` does not contain that key ID, signature fails.
2. If the public key from `content` doesn't verify the signature, signature fails.

Note that ["checking for a signature"](https://spec.matrix.org/v1.7/appendices/#checking-for-a-signature) may
require an update on Step 3 to account for `m.room.send_key` as a place to "look up" verification keys.

**Change 3**: Allow the `sender` to be considered "in the room" for purposes of event auth, provided they aren't
banned or explicitly in the `leave` state.

Immediately prior to Rule 5 of the authorization rules, the following new rules are inserted:

> 5. If the `sender`'s current membership state is `ban`, reject.
> 6. If the `sender` has a current `m.room.member` state event with `membership` of `leave`, reject.
> 7. If the event uses "send keys" (see Rules 2.5 and 2.6), consider the `sender` as joined for rules which follow.
> 8. [unchanged] If the `sender`'s current membership state is not `join`, reject.

(Rule 5 becomes Rule 8 with these additions)

Note that [Server ACLs](https://spec.matrix.org/v1.7/server-server-api/#server-access-control-lists-acls) still
apply to events using send keys. Server ACLs are not currently part of event authorization, but do apply at a
transport level.

**Change 4**: Prevent send keys from changing/adding/removing send keys as a safety measure.

Immediately after Rule 3 of the authorization rules, the following rule is inserted:

> 4. If `type` is `m.room.send_key`:
> 1. If the event uses "send keys" (see Rules 2.5 and 2.6), reject.

## Potential issues

The potential issues with this MSC are primarily security considerations.

## Alternatives

Pseudo IDs of some variety, or senders-as-keys, might be an easier/different way to solve this MSC's
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty concerned that this stack of MSCs collides almost directly with MSC #4014 and #4080. while aiming solve slightly different problems, the changes hit in almost precisely the same place. Once we finally get back to senders-as-keys I think we should reconcile the two together rather than having competing stacks of MSCs.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This MSC is not meant to be conflicting, but indeed all the MSCs involved will have to consider each other's use cases before moving forward.

problem scope.

## Security considerations

A major consideration is what the private key actually allows an attacker to do if they get their hands
on it. Matrix also uses eventual consistency which allows for events using an "old" send key to potentially
be surfaced. The soft failure and rejection characteristics are primarily designed for these two problems.

This MSC requires that the send key signature cover the entire event, just as the origin server's
signature does. If the signature were to instead cover a subset of the event, the event contents could
be easily duplicated with distinct event IDs. This creates an easy spam vector: if the signature covered,
for example, `sender`, `room_id`, and `content` then a malicious sender/server could simply copy/paste
the send key signature and change other details like `prev_events` to flood the room with distinct events
at nearly no cost to them. Instead, by covering the whole event the worst an attacker can do is send
the exact same event ID over and over, which quickly becomes pointless.

Note that the spam vector here is subtly (and dangerously) different to a normal spam vector in Matrix.
It's currently possible for a malicious server to quickly generate billions of events with distinct IDs,
but critically it's possible to kick that server/sender out of the room in a multitude of ways, blocking
further spam from reaching the room. With send keys though, the server does not need to be a member of
the room to spam it. Those servers are reliant on another member server to send their events though, and
therefore can be easily rate limited. A member server colluding with an external server to bypass rate
limiting might not be easily detected by human moderators/admins, and potentially impossible via monitoring
at the federation level. There is not currently a proposed solution to this particular problem.

One option might be to have the "delivering server" of an event using send keys to additionally sign the
event itself, but if an event is coming over `/backfill` or similar then the signature from the original
server which delivered it to the room would be lost, making automated monitoring difficult.

This MSC does not consider participation and "dealing with the DAG" as sufficient barriers to spam. Nor
does it consider invites (both explicit and implicit through room alias knowledge) as a reasonable measure
against having malicious servers in a room. A server can always *become* malicious after proving its
innocence.

To prevent sender-specific spam, this MSC ensures it does not prevent a user from being banned or server
being ACL'd if it attempts to evade the user bans, for example.

Keys stored in `m.room.send_keys` should also be rotated frequently enough to reduce exposure risk. Every
few days should be sufficient for most use cases. Note that rotating the key does not prevent an old key
from being used, as discussed earlier in this section, but does cause the sent event to be soft-failed.

## Unstable prefix

While this proposal is not incorporated into a stable room version, implementations should use `org.matrix.msc4047`
as an unstable room version. The event type `m.room.send_key` should additionally be prefixed as
`org.matrix.msc4047.send_key` for clarity during development.

## Footnotes

[^1]: When auth events can reference or affect each other there is a potential unbreakable loop. This MSC
makes efforts to ensure that `m.room.send_key` cannot be used to change itself, but this doesn't always work
for auth events spread over multiple events. Role-Based Access Control (RBAC) for example is best represented
as multiple state events which can (theoretically) modify each other. If Alice is trying to use their role to
modify Bob's role, but Bob is trying to use their role to prevent Alice from modify that same role, who wins?
There's some things we can do in the event design to prevent this, such as define a "power level" alongside
the role, but sometimes it's just easier to copy `m.room.power_levels` and cram everything into a single event.
This MSC takes the route of cramming everything into a single event, and blocks attempts to use send keys to
add/change/remove send keys for safety.
Loading