diff --git a/proposals/4268-encrypted-history-sharing.md b/proposals/4268-encrypted-history-sharing.md new file mode 100644 index 00000000000..0144e096014 --- /dev/null +++ b/proposals/4268-encrypted-history-sharing.md @@ -0,0 +1,478 @@ +# MSC4268: Sharing room keys for past messages + +In Matrix, rooms can be configured via the +[`m.room.history_visibility`](https://spec.matrix.org/v1.17/client-server-api/#room-history-visibility) +state event such that previously-sent messages can be visible to users that +join the room. However, this is ineffective in encrypted rooms, where new +joiners will lack the keys necessary to decrypt historical messages. + +This proposal defines a mechanism by which existing room members can share the +decryption keys with new members, for example when inviting them, thus giving +the new members access to historical messages. + +A previous proposal, +[MSC3061](https://github.com/matrix-org/matrix-spec-proposals/pull/3061) aimed +to solve a similar problem; however, the mechanism used did not scale well. In +addition, the implementation in `matrix-js-sdk` was subject to a [security +vulnerability](https://matrix.org/blog/2024/10/security-disclosure-matrix-js-sdk-and-matrix-react-sdk/) +which this proposal addresses. + +## Proposal + +### Room key bundle format + +When Alice is about to invite Bob to a room, she first assembles a "room key +bundle" containing all of the room keys for megolm sessions that she believes +future members of the room should have access to. Specifically, those are the +megolm sessions associated with that room which were marked with +`shared_history`: see [below](#shared_history-property-in-mroom_key-events). + +It is RECOMMENDED that Alice first makes sure she has downloaded any keys for +the relevant room from the server-side key storage. + +The keys are assembled into a JSON object with the following structure: + +```json5 +{ + "room_keys": [ + { + "algorithm": "m.megolm.v1.aes-sha2", + "room_id": "!Cuyf34gef24t:localhost", + "sender_claimed_keys": { "ed25519": "aj40p+aw64yPIdsxoog8jhPu9i7l7NcFRecuOQblE3Y" }, + "sender_key": "RF3s+E7RkTQTGF2d8Deol0FkQvgII2aJDf3/Jp5mxVU", + "session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ", + "session_key": "AgAAAADxKHa9uFxcXzwYoNueL5Xqi69IkD4sni8Llf..." + }, + // ... etc + ], + "withheld": [ + { + "algorithm": "m.megolm.v1.aes-sha2", + "code": "m.history_not_shared", + "reason": "History not shared", + "room_id": "!Cuyf34gef24t:localhost", + "sender_key": "RF3s+E7RkTQTGF2d8Deol0FkQvgII2aJDf3/Jp5mxVU", + "session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ" + } + ] +} +``` + +The properties in the object are defined as: + + * `room_keys`: an array of objects each with the following fields from [`ExportedSessionData`](https://spec.matrix.org/v1.17/client-server-api/#definition-exportedsessiondata): + + * `algorithm` + * `room_id` + * `sender_claimed_keys` + * `sender_key` + * `session_id` + * `session_key` + + `forwarding_curve_key_chain` is omitted since it is useless in this case. + The `shared_history` flag defined below is omitted (it is `true` by + implication). + + * `withheld`: an array of objects with the same format as the content of an + [`m.room_key.withheld`](https://spec.matrix.org/v1.17/client-server-api/#mroom_keywithheld) + message, usually with code `m.history_not_shared` (see + [below](#new-withheld-code)) to indicate that the recipient isn't allowed to + receive the key. + +A single session MUST NOT appear in both the `room_keys` and `withheld` +sections. Handling such malformed bundles as a receiving client is +implementation-defined. + +The JSON object is then encrypted using the same algorithm as [encrypted +attachments](https://spec.matrix.org/v1.17/client-server-api/#sending-encrypted-attachments) +(i.e., with AES256-CTR), and uploaded with [`POST +/_matrix/media/v3/upload`](https://spec.matrix.org/v1.17/client-server-api/#post_matrixmediav3upload). + +The details of this key bundle are then shared with Bob, as below. + +### `m.room_key_bundle` to-device message + +Having uploaded the encrypted key bundle, Alice must share the details with each of Bob's devices. + +She first ensures she has an up-to-date list of his devices (performing a +[`/keys/query`](https://spec.matrix.org/v1.17/client-server-api/#post_matrixclientv3keysquery) +request if necessary). She then sends a to-device message to each of his devices +**which are correctly signed by his cross-signing keys**. + +A new to-device message type is defined, `m.room_key_bundle`, which MUST be +encrypted using +[Olm](https://spec.matrix.org/v1.17/client-server-api/#molmv1curve25519-aes-sha2). + +The plaintext content of such a message should be: + +``` +{ + "type": "m.room_key_bundle", + "content": { + "room_id": "!Cuyf34gef24t:localhost", + "file": { + "v": "v2", + "url": "mxc://example.org/FHyPlCeYUSFFxlgbQYZmoEoe", + "key": { + "alg": "A256CTR", + "ext": true, + "k": "aWF6-32KGYaC3A_FEUCk1Bt0JA37zP0wrStgmdCaW-0", + "key_ops": ["encrypt","decrypt"], + "kty": "oct" + }, + "iv": "w+sE15fzSc0AAAAAAAAAAA", + "hashes": { + "sha256": "fdSLu/YkRx3Wyh3KQabP3rd6+SFiKg5lsJZQHtkSAYA" + } + } + }, + "sender": "@alice:example.com", + "recipient": "@bob:example.org", + "recipient_keys": { "ed25519": "" } + "keys": { "ed25519": "" }, + "sender_device_keys": { ... } +} +``` + +The properties within the `content` are defined as: + + * `room_id`: the room to which the keys in the key bundle relate. (This is + required so that Bob can defer downloading the bundle until he joins the room.) + + * `file`: `EncryptedFile` from the [encrypted attachment + format](https://spec.matrix.org/v1.13/client-server-api/#extensions-to-mroommessage-msgtypes). + +`sender`, `recipient`, `recipient_keys` and `keys` are the normal fields +required by Olm-encrypted messages. + +`sender_device_keys` are the sender's device keys, as defined by +[MSC4147](https://github.com/matrix-org/matrix-spec-proposals/pull/4147), which +are **required** for `m.room_key_bundle` messages. The sender MUST include +them, and recipients SHOULD ignore `m.room_key_bundle` messages which omit +them. + + +### `shared_history` property in `m.room_key` messages + +Suppose Alice and Bob are participating in an encrypted room, and Bob now +wishes to invite Charlie to join the chat. If the [history +visibility](https://spec.matrix.org/v1.17/client-server-api/#room-history-visibility) +settings allow, Bob can share the message decryption keys for previously sent +messages with Charlie. However, it is dangerous for Bob to take the server's +word for the history visibility setting: a malicious server admin collaborating +with Charlie could tell Bob that the history visibility was open when in fact +it was restricted. In addition, the history visibility in a given room may have +been changed over time and it can be difficult for clients to establish which +setting was in force for a particular Megolm session. + +To counter this, we add a `shared_history` property to +[`m.room_key`](https://spec.matrix.org/v1.17/client-server-api/#mroom_key) +messages, indicating that the creator of that Megolm session understands and +agrees that the session keys may be shared with newly-invited users in +future. For example: + +```json +{ + "type": "m.room_key", + "content": { + "algorithm": "m.megolm.v1.aes-sha2", + "room_id": "!room_id", + "session_id": "session_id", + "session_key": "session_key", + "shared_history": true + } +} +``` + +In other words: when Alice wants to send a message in the room she shares with +Bob, she first checks the `history_visibility`. If it is `shared` or +`world_readable`, then when she sends the Megolm keys to Bob, she sets +`shared_history` to `true`. + +Clients SHOULD show a visual indication to users that their encrypted messages +may be shared with future room members in this way. + +If the history visibility changes in a way that would affect the +`shared_history` flag (i.e., it changes from `joined` or `invited` to `shared` +or `world_readable`, or vice versa), then clients MUST rotate their outbound +megolm session before sending more messages. + +In addition, a `shared_history` property is added to the [`BackedUpSessionData` +type](https://spec.matrix.org/v1.17/client-server-api/#definition-backedupsessiondata) +in key backups (that is, the plaintext object that gets encrypted into the +`session_data` field) and the [`ExportedSessionData` +type](https://spec.matrix.org/v1.17/client-server-api/#definition-exportedsessiondata). In +both cases, the new property is set to `true` if the session was shared with us +with `shared_history: true`, and `false` otherwise. + +For example: + +```json +{ + "algorithm": "m.megolm.v1.aes-sha2", + "forwarding_curve25519_key_chain": [ + "hPQNcabIABgGnx3/ACv/jmMmiQHoeFfuLB17tzWp6Hw" + ], + "sender_claimed_keys": { + "ed25519": "aj40p+aw64yPIdsxoog8jhPu9i7l7NcFRecuOQblE3Y" + }, + "sender_key": "RF3s+E7RkTQTGF2d8Deol0FkQvgII2aJDf3/Jp5mxVU", + "session_key": "AgAAAADxKHa9uFxcXzwYoNueL5Xqi69IkD4sni8Llf...", + "shared_history": true +} +``` + +In all cases, an absent or non-boolean `shared_history` property is treated the same as +`shared_history: false`. + +### New "withheld" code + +The spec currently +[defines](https://spec.matrix.org/v1.17/client-server-api/#mroom_keywithheld) a +number of "withheld" codes which are used to indicate that a client is +deliberately *not* sharing a megolm session key with another. Normally these +codes are used in `m.room_key.withheld` to-device events; as the text above +specifies, we will now also use them in the `withheld` section of the room key bundle. + +This MSC proposes the addition of a new withheld code, `m.history_not_shared`, +which is used specifically to indicate that the megolm session in question does not +have the `shared_history` flag set (which means that the creator of that +session believed that the room history visibility did not allow new members to +access history). + +The `m.history_not_shared` withheld code SHOULD NOT be used in +`m.room_key.withheld` messages, since they would not be meaningful there. + +* Aside: the spec currently contains a definition for a `withheld` code + `m.unauthorised`. However, its semantics are unclear: the spec defines it as + meaning "the user/device is not allowed to have the key", but is unclear + about why this might happen. (Arguably, `m.blacklisted` and `m.unverified` + are also cases of "the user/device is not allowed to have the key".) + + In practice, modern Element clients (including Element Web and the classic + mobile clients, since the port to the Rust crypto stack), do not send this + withheld code at all. Further, the example given in the spec, "the + user/device was not in the room when the original message was sent", is + somewhat similar to this usecase. + + It is therefore somewhat tempting to repurpose `m.unauthorised` to suit this + usecase. However, `m.unauthorised` has been used for other purposes in the + past (for example, [Element Android + Classic](https://github.com/element-hq/element-android/blob/v1.6.5/matrix-sdk-android/src/kotlinCrypto/java/org/matrix/android/sdk/internal/crypto/IncomingKeyRequestManager.kt#L276) + used to use it as a general-purpose refusal to respond to key requests from + other users), and we have little insight as to how `m.unauthorised` might be + used in non-Element clients. + + In short, a new code is likely to cause less confusion than repurposing + `m.unauthorised`. + +### Actions as a receiving client + +When Bob's client receives an `m.room_key_bundle` event from Alice, there are two possibilities: + + * If Bob has recently accepted an invite to the room from Alice, the client + should immediately download the key bundle and start processing it. Note, + however, that this process must be resilient to Bob's client being restarted + before the download/import completes. + + The definition of "recently" is left up to clients. (They should consider + balancing the needs of (a) a user that closes their client just after + joining a room but before the bundle is imported, against (b) the + overhead of attempting to download a key bundle on every + startup. Element-Web and `matrix-rust-sdk` use a value of 24 hours.) + + * Otherwise, Bob's client should store the details of the key bundle but not + download it immediately. If he later accepts an invite to the room from + Alice, his client downloads and processes the bundle at that point. + + Delaying the download in this way avoids a potential DoS vector in which an + attacker can cause the victim to download a lot of useless data. + +Once Bob has downloaded the key bundle, the sessions are imported as they would +be when importing a key export; however: + + * Only keys for the relevant room should be imported. + + * Bob's client should remember who he received the keys from (Alice, in this case), and + MUST show that information to the user, since he has only that user's word + for the authenticity of those sessions. + +Client implementations should note that server implementations may delete or +expire old media that appears unused. They must therefore gracefully handle +download failures due to the key bundle having expired (typically by just giving up +on the attempt to download the bundle, though they could also warn the +user.) + +### Out-of-scope (for now, at least) + +* Ideally, the bundle would be deleted once the recipient has successfully + downloaded it. An implementation is left for a future MSC, such as + [MSC4425](https://github.com/matrix-org/matrix-spec-proposals/pull/4425). This + is tracked at https://github.com/matrix-org/matrix-rust-sdk/issues/5113. + +* In future, we will need to extend the + [`BackedUpSessionData`](https://spec.matrix.org/v1.17/client-server-api/#definition-backedupsessiondata) + structure (used for server-side key backups) and + [`ExportedSessionData`](https://spec.matrix.org/v1.17/client-server-api/#key-export-format) + (used for key exports) to include information about whether the key was + forwarded by another user (and if so, by whom). However, this is redundant + until we have authenticated backups (MSC4048). This is tracked at + https://github.com/matrix-org/matrix-rust-sdk/issues/5110. + +## Potential issues + +* As noted in the "Security considerations" section below, current + implementations make assumptions about when it is necessary to rotate + outgoing megolm sessions that are no longer sufficient. + +* It might be relatively easy to be tricked by a malicious user into downloading + a (potentially very large) key bundle. It's possible that we will need to add + a mechanism for warning the user about this in the future. In the meantime, + the risk is mitigated by the fact that the key bundle size is limited by the + homeserver's media size limit. + +* Users may experience unexpected failures when sending invites because they + have reached a limit in their media quota, or may be surprised to discover + that they have used their entire media quota by inviting people to encrypted + rooms. + + [MSC4425](https://github.com/matrix-org/matrix-spec-proposals/pull/4425) + may help with this in the future, for example by force-expiring ephemeral + media. + +* In theory, the key bundle could exceed the maximum size of media item that is + permitted by either the inviting user's homeserver, or the joining user's + homeserver. + +## Alternatives + +We considered, and dismissed, a number of alternative approaches to solving +this problem: + +* MSC3061, but without the security flaw. + + Where MSC3061 implementations previously shared keys with *all* of the + recipient's devices, we could limit this behaviour to only *verified* + devices, hence mitigating the security concern. + + However, this approach still had performance problems: sending a to-device + message to each recipient device, for each megolm session ever used in the + room, is prohibitive. + +* Shared key store + + We considered whether it was possible for members in a room to somehow + collaborate on building some sort of "shared key store"; new members could + then be granted access. + + However, we were unable to find satisfactory solutions to a number of + practical questions. For example: + + * should the store be populated by all users, or just admins? If the former, + what if a mischievous user uploads incorrect keys? If the latter, what if + no admins are online? + + * What happens if there are users on multiple servers: do we need to + replicate the keystore between servers, or risk the keystore being + unavailable? + + * How do we orchestrate rotating the keystore when a user leaves the room? + +* Request keys from existing room members + + A new joiner could request keys to the room from existing room + members. However, this is (a) intrusive to existing room members, and (b) + very vulnerable to social-engineering attacks. + +These alternatives are also discussed in a presentation made at Matrix Conference +2025: https://youtu.be/_E4ArQopptM?t=168. + +## Security considerations + +* In an encrypted room, senders are required to rotate their outgoing Megolm + sessions after a user leaves the room, to ensure that the now-departed user + cannot decrypt messages sent after they left. + + Currently, most implementations only do this if they have actually shared the + key to the session with the departed user (i.e., typically, they sent at + least one message while the user was in the room); they otherwise assume that + the user does not have a copy of the session decryption key and it is safe to + continue using the same session. + + The latter is no longer a valid assumption. Instead, clients MUST observe + changes in state in the room, and whenever they see a user leaving the room, + assume that the departed user may have access to any existing Megolm session, + and create a new session before sending further encrypted messages. + + Note that, in a `limited` sync, clients must treat any membership event with a + membership other than `join` as an indication that the affected user may have + joined and left the room. + +* The proposed mechanism allows clients to share the decryption keys for + significant amounts of encrypted content. Sharing historical keys in this way + represents a significantly greater security risk than sharing keys for future + messages on an ad-hoc basis, as when sending `m.room_key` messages. + + It is therefore **crucial** that the inviting client take careful measures to + ensure that the recipient devices genuinely belong to the intended + recipient, rather than having been injected by an intruder. + + For example, the recipient must cross-sign his devices, and the sender must + ensure that the devices are correctly signed. Further, the sender should keep + records of cross-signing keys seen for each user, and if a change is + observed, consider this a red flag suggesting that the account may be + compromised and confirm with the user. + +* Similarly, users need to be particularly careful when sending room invites + that the recipient of an invite is in fact the intended user: leaking + encryption keys to a typo-squatter could be catastrophic, for example. + + Clients might consider warning users before they send an invite to a user + that they haven't previously interacted with. (This is tracked for the + Element clients at + [element-meta#3163](https://github.com/element-hq/element-meta/issues/3163).) + +* Recipients must be mindful that there is no authoritative evidence of the + sender of messages decrypted using a room key bundle: a malicious (or buggy) + inviter working in cahoots with a homeserver administrator could make it + appear as though events sent by one user were in fact sent by another. + + Ultimately, the recipient of a key bundle is taking the word of the sender + of that key bundle as to the actual owner of each megolm session. This is an + inevitable consequence of the deniability property of encrypted messaging. + + Recipient clients should make this constraint obvious to the user, for + example by showing the affected messages with a label "Alice shared this message". + +* Recipients should be mindful of the potential of denial-of-service (DoS) and + cache-poisoning attacks from malicious senders. + + In particular, a malicious sender could try to prompt a recipient to download + significant amounts of data from the media store by sending + `m.room_key_bundle` messages pointing to large media files. + + Further, a malicious sender might attempt to make the recipient believe that + a megolm session belonged to Alice, whereas it actually belonged to + Charlie. Even if the recipient later receives a key bundle from an honest + user, they may now have difficulty deciding which user was correct. + + Both problems can be mitigated by only accepting key bundles when accepting + an invite from that user. + +## Unstable prefix + +Until this MSC is accepted, the following identifiers should be used: + + * `io.element.msc4268.room_key_bundle` instead of `m.room_key_bundle` for the + to-device message containing details of the key bundle. + + * `org.matrix.msc3061.shared_history` instead of `shared_history` for the + property in `BackedUpSessionData` and `ExportedSessionData` indicating that + the key can be shared with new members. + + * `io.element.msc4268.history_not_shared` instead of `m.history_not_shared` as + the withheld code for sessions which are not marked as `shared_history`. + +## Dependencies + +This MSC depends on [MSC4147](https://github.com/matrix-org/matrix-spec-proposals/pull/4147), which has recently been accepted into the spec.