You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
matrix-spec-proposals/proposals/4047-room-send-keys.md

16 KiB

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.

The core principle of this problem is tracked as issue #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 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:

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 resolution1.

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

An m.room.send_key looks as such:

{
  // 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:

{
  // 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, 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 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.

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, 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.

{
  "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 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:

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:

  1. [...] 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:

{
  "$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" 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:

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

(Rule 5 becomes Rule 8 with these additions)

Note that Server 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:

  1. 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 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, using room version 11 as a base. The event type m.room.send_key should additionally be prefixed as org.matrix.msc4047.send_key in that room version for clarity during development.

Test vectors

TODO: "This event produces this event ID" sort of idea.

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. ↩︎