Skip to content

Spec PR - MSC4147: Including device keys with Olm-encrypted events #2122

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions changelogs/client_server/newsfragments/2122.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Include device keys with Olm-encrypted events as per [MSC4147](https://github.com/matrix-org/matrix-spec-proposals/pull/4147).
37 changes: 14 additions & 23 deletions content/client-server-api/modules/end_to_end_encryption.md
Original file line number Diff line number Diff line change
Expand Up @@ -1512,20 +1512,7 @@ message.

The plaintext payload is of the form:

```json
{
"type": "<type of the plaintext event>",
"content": "<content for the plaintext event>",
"sender": "<sender_user_id>",
"recipient": "<recipient_user_id>",
"recipient_keys": {
"ed25519": "<our_ed25519_key>"
},
"keys": {
"ed25519": "<sender_ed25519_key>"
}
}
```
{{% definition path="api/client-server/definitions/olm_payload" %}}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I replaced the example with a schema because that allows us to reuse device_keys.yaml.

Copy link
Member

Choose a reason for hiding this comment

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

(and it's just generally Better. We're trying to phase out these old-style json blocks)


The type and content of the plaintext message event are given in the
payload.
Expand All @@ -1536,15 +1523,19 @@ claiming to have sent messages which they didn't. `sender` must
correspond to the user who sent the event, `recipient` to the local
user, and `recipient_keys` to the local ed25519 key.

Clients must confirm that the `sender_key` property in the cleartext
`m.room.encrypted` event body, and the `keys.ed25519` property in the
decrypted plaintext, match the keys returned by
[`/keys/query`](#post_matrixclientv3keysquery) for
the given user. Clients must also verify the signature of the keys from the
`/keys/query` response. Without this check, a client cannot be sure that
the sender device owns the private part of the ed25519 key it claims to
have in the Olm payload. This is crucial when the ed25519 key corresponds
to a verified device.
Clients must ensure that the sending device owns the private part of
the ed25519 key it claims to have in the Olm payload. This is crucial
when the ed25519 key corresponds to a verified device. To perform
this check, clients MUST confirm that the `sender_key` property in the
cleartext `m.room.encrypted` event body, and the `keys.ed25519` property
in the decrypted plaintext, match the keys under the `sender_device_keys`
property. Additionally, clients MUST also verify the signature of the keys.
If `sender_device_keys` is absent, clients MUST retrieve the sender's
keys from [`/keys/query`](#post_matrixclientv3keysquery) instead. This
will not allow them to verify key ownership if the sending device was
logged out or had its keys reset since sending the event. Therefore,
clients MUST populate the `sender_device_keys` property when sending
events themselves.
Comment on lines +1526 to +1538
Copy link
Member

Choose a reason for hiding this comment

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

I don't find this very easy to follow, and I think it's conflating several checks. (The previous text also had this problem, but I think we're making it worse.)

I think we probably want to replace this whole paragraph and the preceding one, with a couple of new h6-level sections to slot in before "Recovering from undecryptable messages". Something like this:


Validation of incoming decrypted events

After decrypting an incoming encrypted event, clients MUST apply the following checks:

  1. The sender property in the decrypted content must match the sender of the event.
  2. The keys.ed25519 property in the decrypted content must match the sender_key property in the cleartext m.room.encrypted event body.
  3. The recipient property in the decrypted content must match the user ID of the local user.
  4. The recipient_keys.ed25519 property in the decrypted content must match the client device's Ed25519 signing key.
  5. Where sender_device_keys is present in the decrypted content:
    1. sender_device_keys.user_id must also match the sender of the event.
    2. sender_device_keys.keys.ed25519:<device_id> must also match the sender_key property in the cleartext m.room.encrypted event body.
    3. sender_device_keys.keys.curve25519:<device_id> must match the Curve25519 key used to establish the Olm session.
    4. The sender_device_keys structure must have a valid signature from the key with ID ed25519:<device_id> (i.e., the sending device's Ed25519 key).

Any event that does not comply with these checks MUST be discarded.

Verification of the sending user for incoming events

In addition, for each Olm session, clients MUST verify that the Curve25519 key used to establish the Olm session does indeed belong to the claimed sender. This requires a signed "device keys" structure for that Curve25519 key, which can be obtained in one of two ways:

  1. An Olm message may be received with a sender_device_keys property in the decrypted content.
  2. The keys are returned via a /keys/query request. Note that both the Curve25519 key and the Ed25519 key in the returned device keys structure must match those used in an Olm-encrypted event as above. (In particular, the Ed25519 key must be present in the encrypted content of an Olm-encrypted event to prevent an attacker from claiming another user's Curve25519 key as their own.)

Ownership of the Curve25519 key is then established in one of two ways:

  1. Via cross-signing. For this to be sufficient, the device keys structure must be signed by the sender's self-signing key, and that self-signing key must itself have been validated (either via explicit verification or a TOFU mechanism).
  2. Via explicit verification of the device's Ed25519 signing key, as contained in the device keys structure. This is no longer recommended.

A failure to complete these verifications does not necessarily mean that the session is bogus; however it is the case that there is no proof that the claimed sender is accurate, and the user should be warned accordingly.


Phew, that ended up longer than I expected. Sorry to pile this cleanup work onto this PR. @dkasak: would you mind checking that I haven't missed anything here?


If a client has multiple sessions established with another device, it
should use the session from which it last received and successfully
Expand Down
88 changes: 88 additions & 0 deletions data/api/client-server/definitions/olm_payload.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Copyright 2025 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: OlmPayload
description: |-
The plaintext payload of Olm message events.
properties:
type:
type: string
description: The type of the event.
content:
type: object
description: The event content.
sender:
type: string
description: The user ID of the event sender.
recipient:
type: string
description: The user ID of the intended event recipient.
recipient_keys:
description: The recipient's signing keys of the encrypted event.
$ref: "#/components/schemas/SigningKeys"
keys:
$ref: "#/components/schemas/SigningKeys"
description: The sender's signing keys of the encrypted event.
sender_device_keys:
$ref: device_keys.yaml
description: The sender's device keys.
x-addedInMatrixVersion: "1.15"
required:
- type
- content
- sender
- recipient
- recipient_keys
- keys
components:
schemas:
SigningKeys:
type: object
title: SigningKeys
description: Public keys used for an `m.olm.v1.curve25519-aes-sha2` event.
properties:
ed25519:
type: string
description: The Ed25519 public key encoded using unpadded base64.
required:
- ed25519
example: {
"type": "<type of the plaintext event>",
"content": "<content for the plaintext event>",
"sender": "<sender_user_id>",
"recipient": "<recipient_user_id>",
"recipient_keys": {
"ed25519": "<our_ed25519_key>"
},
"keys": {
"ed25519": "<sender_ed25519_key>"
},
"sender_device_keys": {
"algorithms": ["<supported>", "<algorithms>"],
"user_id": "<user_id>",
"device_id": "<device_id>",
"keys": {
"ed25519:<device_id>": "<sender_ed25519_key>",
"curve25519:<device_id>": "<sender_curve25519_key>"
},
"signatures": {
"<user_id>": {
"ed25519:<device_id>": "<device_signature>",
"ed25519:<ssk_id>": "<ssk_signature>",
}
}
}
}