Specify history sharing (#2399)
Some checks failed
Spec / 🔎 Validate OpenAPI specifications (push) Has been cancelled
Spec / 🔎 Check Event schema examples (push) Has been cancelled
Spec / 🔎 Check OpenAPI definitions examples (push) Has been cancelled
Spec / 🔎 Check JSON Schemas inline examples (push) Has been cancelled
Spec / ⚙️ Calculate baseURL for later jobs (push) Has been cancelled
Spec / 📢 Run towncrier for changelog (push) Has been cancelled
Spell Check / Spell Check with Typos (push) Has been cancelled
Spec / 🐍 Build OpenAPI definitions (push) Has been cancelled
Spec / 📖 Build the spec (push) Has been cancelled
Spec / 🔎 Validate generated HTML (push) Has been cancelled
Spec / 📖 Build the historical backup spec (push) Has been cancelled
Spec / Create release (push) Has been cancelled

Co-authored-by: Kévin Commaille <76261501+zecakeh@users.noreply.github.com>
Co-authored-by: Johannes Marbach <n0-0ne+github@mailbox.org>
Co-authored-by: Hubert Chathi <hubertc@matrix.org>
This commit is contained in:
Richard van der Hoff 2026-06-24 12:00:12 +01:00 committed by GitHub
parent 6c64f583e4
commit 68ffc62de3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 420 additions and 48 deletions

View file

@ -0,0 +1 @@
Specify encrypted history sharing, as per [MSC4268](https://github.com/matrix-org/matrix-spec-proposals/pull/4268).

View file

@ -1547,6 +1547,143 @@ objects described as follows:
{{% definition path="api/client-server/definitions/megolm_export_session_data" %}}
#### Sharing keys between users
{{% added-in v="1.19" %}}
When Alice invites Bob to an encrypted room, she might want Bob to have access
to messages that were previously sent in that room, subject to the [history
visibility](#room-history-visibility) setting of the room.
Alice does this by constructing an [encrypted key
bundle](#construction-and-sharing-of-the-key-bundle) and sharing it with Bob
before inviting him.
##### Shareable encryption sessions
Only room keys which were marked as "shareable" by the creator of the
encryption session should be shared with new users.
When a user wants to send a message in an encrypted room, their client first
checks the [history visibility](#room-history-visibility) state of the
room. If it is `shared` or `world_readable`, then when the client sends the Megolm
keys to room members via [`m.room_key`](#mroom_key) messages, it SHOULD set
`shared_history` to `true`. Otherwise, it SHOULD set `shared_history` to `false`.
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.
Clients SHOULD show an indication to users that their encrypted messages
may be shared with future room members in this way.
Recipients SHOULD keep a record of the `shared_history` flag for each
encryption session. The state of the flag is also saved in the
[`BackedUpSessionData`](#definition-backedupsessiondata) type in key backups,
and the [`ExportedSessionData`](#definition-exportedsessiondata) type in key
exports.
A session is therefore considered as "shareable" if any of the following
conditions are satisfied:
* The client created the session itself, when the `history_visibility` state
was set to `shared` or `world_readable`.
* The keys to the session were received from an [`m.room_key`](#mroom_key)
message with `shared_history` set to `true`.
* The keys to the session were loaded from key backup, and the
[`BackedUpSessionData`](#definition-backedupsessiondata) structure had
`shared_history` set to `true`.
* The keys to the session were loaded from a key export, and the
[`ExportedSessionData`](#definition-exportedsessiondata) structure had
`shared_history` set to `true`.
* The keys were received as part of a
[`RoomKeyBundle`](#definition-roomkeybundle).
{{% boxes/note %}}
Tracking shareable sessions in this way prevents an attack where a malicious
homeserver can incorrectly flag history as shared, without telling the sender
of the messages. It also ensures that a user that sends an encrypted message does
so in the knowledge that the message may be shared with new members in the
room.
{{% /boxes/note %}}
##### Construction and sharing of the key bundle
Alice's client MAY choose not to share any room history (even messages sent when the
history visibity setting would allow sharing) if the current history
visibility setting does not allow sharing (i.e. if `history_visibility` is
set to `invited` or `joined`).
Otherwise, before inviting Bob to a room, Alice's client constructs and sends a key bundle as follows:
1. Alice's client SHOULD ensure that it has downloaded all keys relevant to the room
from [server-side key backup](#server-side-key-backups), if she is using it.
2. Alice's client constructs a [`RoomKeyBundle`](#definition-roomkeybundle) structure,
containing the sessions she is aware of in the room. Alice MUST include
only [shareable encryption sessions](#shareable-encryption-sessions) in the
`room_keys` section of the structure; other sessions SHOULD be listed in the
`withheld` section.
3. The client serialises the `RoomKeyBundle` as JSON.
4. Alice's client encrypts and uploads the serialised JSON in the same way as when
[sending an encrypted attachment](#sending-encrypted-attachments).
5. Alice's client ensures she has an up-to-date list of Bob's devices (performing a
[`/keys/query`](#post_matrixclientv3keysquery) request if necessary).
6. For each of Bob's devices which are correctly
[cross-signed](#cross-signing), Alice's client encrypts and sends an
[`m.room_key_bundle`](#mroom_key_bundle) message.
Alice's client MUST NOT send the `m.room.key_bundle` message to devices that have not
been correctly cross-signed by their owner, due to the risk of sharing
significant amounts of encrypted content with an attacker-controlled device.
{{% definition path="api/client-server/definitions/room_key_bundle" %}}
{{% event event="m.room_key_bundle" %}}
##### Receiving a key bundle event
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 and decrypt 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. 24 hours is a
recommended time limit.)
* 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 large quantity of useless data.
Once Bob has downloaded and decrypted the key bundle, the sessions are imported
as they would be when importing a [key export](#key-exports); however:
* Only keys for the relevant room should be imported. Keys for other rooms
SHOULD be ignored.
* Bob's client MUST 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.)
#### Messaging Algorithms
##### Messaging Algorithm Names
@ -1830,7 +1967,7 @@ In order to enable end-to-end encryption in a room, clients can send an
When creating a Megolm session in a room, clients must share the
corresponding session key using Olm with the intended recipients, so
that they can decrypt future messages encrypted using this session. An
`m.room_key` event is used to do this. Clients must also handle
[`m.room_key`](#mroom_key) event is used to do this. Clients must also handle
`m.room_key` events sent by other devices in order to decrypt their
messages.
@ -1845,6 +1982,36 @@ When a client is updating a Megolm session in its store, the client MUST ensure:
user, or from a `m.room_key` event.
* that the new session key has a lower message index than the existing session key.
When encrypting outgoing messages in a room using Megolm, clients MUST rotate
their outgoing Megolm session (i.e. discard the existing session, and create
and share a new session before sending more room messages) whenever any of the
following happens:
* The existing session has been in use for longer than the period specified in
`rotation_period_ms` in the [`m.room.encryption`](#mroomencryption) room
state event, or an appropriate default.
* The existing session has been used to encrypt as many messages as specified in
`rotation_period_msgs` in the [`m.room.encryption`](#mroomencryption) room
state event, or an appropriate default.
* A user or device that was previously participating in the room, and may have
received a copy of the decryption keys for the session, is seen to leave the
room.
{{% changed-in v="1.19" %}} Since any user that received an invite to the
room may have received a copy of the decryption keys for the session via
[history sharing](#sharing-keys-between-users), 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
rotate the session. Note that, in a `limited` [sync](#syncing), 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.
* {{% added-in v="1.19" %}} The [history visibility](#room-history-visibility)
state of the room changes in a way that would affect the `shared_history`
flag: see [shareable encryption sessions](#shareable-encryption-sessions).
#### Protocol definitions
##### Events

View file

@ -49,6 +49,12 @@ properties:
description: |-
Unpadded base64-encoded session key in [session-export format](https://gitlab.matrix.org/matrix-org/olm/blob/master/docs/megolm.md#session-export-format).
example: "AgAAAADxKHa9uFxcXzwYoNueL5Xqi69IkD4sni8Llf..."
shared_history:
x-addedInMatrixVersion: "1.19"
type: boolean
description: |-
Whether the session is [shareable](/client-server-api/#shareable-encryption-sessions).
example: false
required:
- algorithm
- forwarding_curve25519_key_chain

View file

@ -0,0 +1,109 @@
# Copyright 2026 The Matrix.org Foundation C.I.C.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
type: object
title: RoomKeyBundle
description: |
A bundle of room keys and withheld indications, sent from one user to another, to share encrypted history.
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.
properties:
room_keys:
description: |
The room keys to be shared with the recipient of the bundle.
The data type is similar to the format used for [key exports](/client-server-api/#key-export-format),
but omits `forwarding_curve25519_key_chain` and `shared_history`.
type: array
items:
type: object
title: HistoricRoomKey
description: |-
The format of a session key, when shared as part of a `RoomKeyBundle`.
properties:
algorithm:
type: string
description: |-
The end-to-end message encryption algorithm that the key is for. Must be `m.megolm.v1.aes-sha2`.
example: "m.megolm.v1.aes-sha2"
room_id:
type: string
format: mx-room-id
pattern: "^!"
description: |-
The room where the session is used.
example: "!Cuyf34gef24t:localhost"
sender_claimed_keys:
type: object
additionalProperties:
type: string
format: mx-unpadded-base64
description: |-
A map from algorithm name (`ed25519`) to the Ed25519 signing key of
the device which initiated the session originally, according to the
creator of this key bundle.
example: { "ed25519": "aj40p+aw64yPIdsxoog8jhPu9i7l7NcFRecuOQblE3Y" }
sender_key:
type: string
format: mx-unpadded-base64
description: |-
Unpadded base64-encoded device Curve25519 key, of the device which
initiated the session originally, according to the creator of this
key bundle.
example: "RF3s+E7RkTQTGF2d8Deol0FkQvgII2aJDf3/Jp5mxVU"
session_id:
type: string
description: |-
The Megolm session ID.
example: "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ"
session_key:
type: string
format: mx-unpadded-base64
description: |-
Unpadded base64-encoded session key in [session-export
format](/olm-megolm/megolm/#session-export-format).
example: "AgAAAADxKHa9uFxcXzwYoNueL5Xqi69IkD4sni8Llf..."
required:
- algorithm
- room_id
- sender_claimed_keys
- sender_key
- session_id
- session_key
withheld:
description: |-
The room keys that the creator of the bundle is choosing not to share
with the recipient.
The `code` will normally be `m.history_not_shared`, to indicate that the
recipient isn't allowed to receive the key.
type: array
items:
allOf:
- $ref: "../../../event-schemas/schema/components/room_key_withheld_content.yaml"
- title: RoomKeyWithheld
example: {
"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"
}

View file

@ -5,6 +5,7 @@
"algorithm": "m.megolm.v1.aes-sha2",
"room_id": "!Cuyf34gef24t:localhost",
"session_id": "X3lUlvLELLYxeTx4yOVu6UDpasGEVO0Jbu+QFnm0cKQ",
"session_key": "AgAAAADxKHa9uFxcXzwYoNueL5Xqi69IkD4sni8LlfJL7qNBEY..."
"session_key": "AgAAAADxKHa9uFxcXzwYoNueL5Xqi69IkD4sni8LlfJL7qNBEY...",
"shared_history": false
}
}

View file

@ -0,0 +1,21 @@
{
"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"
}
}
}
}

View file

@ -0,0 +1,49 @@
type: object
properties:
algorithm:
type: string
enum: ["m.megolm.v1.aes-sha2"]
description: |-
The encryption algorithm for the key that this event is about.
room_id:
type: string
format: mx-room-id
pattern: "^!"
description: |-
Required if `code` is not `m.no_olm`. The room for the key that
this event is about.
session_id:
type: string
description: |-
Required if `code` is not `m.no_olm`. The session ID of the key
that this event is about.
sender_key:
type: string
format: mx-unpadded-base64
description: |-
The unpadded base64-encoded device curve25519 key of the event\'s
sender.
code:
type: string
enum:
- m.blacklisted
- m.unverified
- m.unauthorised
- m.unavailable
- m.no_olm
- m.history_not_shared
description: |-
A machine-readable code for why the key was not sent. Codes beginning
with `m.` are reserved for codes defined in the Matrix
specification. Custom codes must use the Java package naming
convention.
reason:
type: string
description: |-
A human-readable reason for why the key was not sent. The receiving
client should only use this string if it does not understand the
`code`.
required:
- algorithm
- sender_key
- code

View file

@ -8,7 +8,7 @@ description: |-
This event type is used to indicate that the sender is not sharing room keys
with the recipient. It is sent as a to-device event.
Possible values for `code` include:
The following values for `code` are defined:
* `m.blacklisted`: the user/device was blacklisted.
* `m.unverified`: the user/device was not verified, and the sender is only
@ -19,6 +19,13 @@ description: |-
* `m.unavailable`: sent in reply to a key request if the device that the
key is requested from does not have the requested key.
* `m.no_olm`: an olm session could not be established.
* **[Added in `v1.19`]** `m.history_not_shared`: when used as part of a
[`RoomKeyBundle` structure](/client-server-api/#definition-roomkeybundle),
indicates to the recipient of the bundle that the corresponding room key
was not marked as
[shareable](/client-server-api/#shareable-encryption-sessions) by the
sender. Clients SHOULD NOT use this `code` in a `m.room_key.withheld`
event.
In most cases, this event refers to a specific room key. The one exception to
this is when the sender is unable to establish an olm session with the
@ -36,51 +43,7 @@ description: |-
room messages sent before it receives the message.
properties:
content:
properties:
algorithm:
type: string
enum: ["m.megolm.v1.aes-sha2"]
description: |-
The encryption algorithm for the key that this event is about.
room_id:
type: string
description: |-
Required if `code` is not `m.no_olm`. The room for the key that
this event is about.
session_id:
type: string
description: |-
Required if `code` is not `m.no_olm`. The session ID of the key
that this event is about.
sender_key:
type: string
description: |-
The unpadded base64-encoded device curve25519 key of the event\'s
sender.
code:
type: string
enum:
- m.blacklisted
- m.unverified
- m.unauthorised
- m.unavailable
- m.no_olm
description: |-
A machine-readable code for why the key was not sent. Codes beginning
with `m.` are reserved for codes defined in the Matrix
specification. Custom codes must use the Java package naming
convention.
reason:
type: string
description: |-
A human-readable reason for why the key was not sent. The receiving
client should only use this string if it does not understand the
`code`.
required:
- algorithm
- sender_key
- code
type: object
$ref: "components/room_key_withheld_content.yaml"
type:
enum:
- m.room_key.withheld

View file

@ -25,6 +25,19 @@ properties:
session_key:
type: string
description: The key to be exchanged.
shared_history:
x-addedInMatrixVersion: "1.19"
type: boolean
description: |
`true` indicates that the creator of this encryption key considers that
the session is [shareable](/client-server-api/#shareable-encryption-sessions):
in other words, the sender has observed that the [room history
visibility](/client-server-api/#room-history-visibility) is set to
`shared` or `world_readable`, and that they understand and agree that
the session keys may be shared with newly-invited users in future.
Absence, or any other value, indicates that the creator of the
session does not consider the session to be shareable.
required:
- algorithm
- room_id

View file

@ -0,0 +1,42 @@
---
$schema: https://json-schema.org/draft/2020-12/schema
allOf:
- $ref: core-event-schema/event.yaml
description: |-
This event type is used to share a bundle of room keys when inviting a new user to the room.
It is encrypted as an
[`m.room.encrypted`](/client-server-api/#mroomencrypted) [to-device
event](/client-server-api/#send-to-device-messaging) using
[Olm](/client-server-api/#molmv1curve25519-aes-sha2).
The `sender_device_keys` property in the [Olm
plaintext](/client-server-api/#definition-olmpayload) MUST be
populated. Recipients SHOULD ignore `m.room_key_bundle` messages which omit
them.
properties:
content:
properties:
room_id:
type: string
format: mx-room-id
pattern: "^!"
description: The room to which the keys in the key bundle relate.
file:
description: |-
An [EncryptedFile](/client-server-api/#definition-encryptedfile)
structure. The location and decryption keys for the encrypted
[`RoomKeyBundle`](/client-server-api/#definition-roomkeybundle).
title: EncryptedFile
type: object
required:
- room_id
- file
type: object
type:
enum:
- m.room_key_bundle
type: string
type: object