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/4080-cryptographic-identiti...

28 KiB

MSC4080: Cryptographic Identities (Client-Owned Identities)

THIS MSC IS IN PROGRESS AND WILL CHANGE AS IMPLEMENTATIONS LAND

Todays Matrix does not allow users to move their account between homeservers. It would be beneficial to be able to move a user account from one homeserver to another while allowing that user to maintain their existing room memberships, power levels in those rooms, message history, account data and end-to-end encrypted sessions.

With Pseudonymous Identities (pseudoIDs) we have decoupled a users mxid from the identity used to track room membership. The new pseudoIDs (also known as user_room_keys/senderids) are both created and managed entirely by the homeserver. PseudoIDs was the first step towards having portable accounts in Matrix.

With Cryptographic Identities we aim to take portable accounts one step further by moving the Pseudonymous Identities off the server and onto the client in order for the client to have full ownership over their identity. Clients are then responsible for performing full event signing on a per-event basis. This step brings us closer to portable accounts primarily in two ways.

  1. Users now fully own their identity in the Matrix ecosystem and have the control to move their identity.
  2. It improves the security of the Matrix ecosystem by making it more difficult for a homeserver to act maliciously on a clients behalf. This is particularly important once accounts are portable in order to prevent a homeserver from being able to continue operating on a users behalf after that user has moved their account off the homeserver.

Cryptographic Identities does not get us all the way to having account portability. Further MSC/s will be required to create the appropriate endpoints and other changes in order to successfully port a users account from one homeserver to another.

Proposal

CryptoIDs are generated and stored by the client. When joining a room for the first time, a cryptoID should be generated for that room. All events are signed by the client using their cryptoID and are no longer signed by the users homeserver with the exception of the mxid_mapping in the m.room.member event.

Event Signing

Events are required to be signed by the cryptoID. In order for this to work with client-owned keys, clients need to obtain the full version of events before they can be signed. This proposal introduces a few changes to the C-S API endpoints used to send events between the client and the server. Any C-S API endpoint which previously was used to send events, now returns the fully formed version of those event/s to the client (minus the signatures block). The event/s are no longer processed by the server while handling these endpoints. The client then signs the event/s and forwards them to the server via a new /send_pdus endpoint. When handling events sent to this new endpoint the server should process the event/s like normal by adding them to their respective rooms.

A homeserver should avoid processing room events from the client until they have been sent via the /send_pdus endpoint to ensure the client actually signs the event so it can be successfully sent into the room.

Endpoint Additions

POST /_matrix/client/v1/send_pdus/{txnId}

Rate-limited: Yes Requires authentication: Yes

Fully formed PDUs are sent to this endpoint to be committed to a room DAG. Clients are expected to have signed the events sent to this endpoint. Homeservers should reject any event which isnt properly signed by the client.

Events sent to this endpoint are processed in the order they are received. A homeserver should check the validity of each event before sending it to the room. This includes verifying the signature of the event matches the cryptoID found in the sender field of the event. If the event is for a remote invite or join, the relevant /send_invite or /send_join over federation should be performed prior to adding the event to the room.

If any event is invalid all events are rejected by the homeserver. Invalid events include those that are not correctly signed, whose event fields are invalid (such as a state event missing a state_key field), or the homeserver deems the event invalid for some other reason. This approach is taken because this failure mode is most likely due to a programming error. Failures of this nature result in a HTTP status 400. A standard error response will be returned. As well as the normal common error codes, other reasons for rejection include:

A homeserver should also protect against clients who modify events sent by the homeserver before signing them. If a client modifies an event, such as changing prev_events to force costly state resolution, then we should reject that event. A homeserver can do this by storing the hash of the proto event in a database, and then on /send_pdus, remove the signatures key and check if the hash exists in the DB (i.e the homeserver sent the client this exact proto event). The homeserver can also then expire the proto event in a timely manner which helps alleviate issues of costly state resolution due to the likelihood of prev_events changing as time passes. Any kind of client event signing is going to add latency to creating events, which is going to increase the chance of increasing the number of forward extremities.

A txn_id is added to the request parameters. Clients should generate an ID unique across requests with the same access token; it will be used by the server to ensure idempotency of requests.

Request:

{
    pdus:  [
        PDUInfo    
    ]
}

PDUInfo:

{
    room_version:  string,
    via_server:  string,  //  optional
    pdu: PDU  //  signed  PDU
}

Endpoint Changes

Effected endpoint versions all need to be bumped since the underlying behaviour is changed with this proposal. When hitting any of these endpoints the resulting events are no longer immediately added to the room. Instead the client is required to send the returned event/s to the /send_pdus endpoint after signing them in order for the event/s to be added to the room DAG.

POST /_matrix/client/v4/createRoom

Room creation adds a new cryptoid field to the request body. The cryptoid must be valid Unpadded Base64 and 32 bytes in size in order to be a valid ed25519 public key. This field is used for the homeserver to be able to fully create all the necessary room creation events on behalf of the client. Since this is a new room the homeserver needs to be told which cryptoID to correlate to this room for this user.

The response includes the new fields: room_version and pdus.

Request:

{
    ...,
    cryptoid: string
}

200 OK Response:

{
    room_id: string,
    room_version: string,
    pdus:  [  PDU ]
}
POST /_matrix/client/v4/rooms/{roomId}/invite

Inviting users to a room has a number of changes in order to make it work. First, since the cryptoID for a given user and room needs to be created by the client, we cannot rely on the existing invite sequence which relies on the invited users homeserver to fully populate the invite event. Instead we need a way for the invited user to be part of the loop and provide a cryptoID in order to finalize the event. It would not be acceptable to require the invited client to be available at all times in order to respond to an invite request in real time. Matrix does not currently have a requirement that client communications be synchronous and this proposal seeks to preserve asynchronous communications when participants are unreachable. Instead, this proposal introduces the concept of one-time cryptoIDs.

One-time cryptoIDs are uploaded to the users homeserver so that they can be claimed and used whenever that user receives a room invite. In order for a user to be available for invite, one-time cryptoIDs should be created and uploaded to a users current homeserver. This should take the same shape as one-time keys for encryption do today. The one-time cryptoIDs should be signed by the devices ed25519 key to verify they were created by that device.

When a client wants to invite a new user to a room for the first time, they need to query the invited users homeserver for one of the invited users one-time cryptoIDs. They can then use that cryptoID to create an invite event for the user.

The invite response includes a new pdu field.

Two new endpoints are also added to the S-S API: /make_invite & /send_invite. These endpoints are required in order to split out generating an invite event, and having the inviting client sign that event, from actually sending the event to the invited users homeserver.

TODO: document /make_invite & /send_invite endpoints

sequenceDiagram
participant Alice
participant Alice HS
participant Bob HS
participant Bob
Bob ->> Bob HS: /keys_upload (one-time cryptoID)
Note over Bob, Bob HS: Occurs separately, when one-time<br/>cryptoID count is low.
Alice ->> Alice HS: /invite:@bob:bob_hs
Alice HS ->> Bob HS: /make_invite
Bob HS ->> Bob HS: Claim one-time cryptoID
Note right of Bob HS: A valid one-time cryptoID is required<br/>to create the full invite event.
Bob HS ->> Alice HS: (proto pdu)
Alice HS ->> Alice: (proto pdu)
Alice ->> Alice: Sign PDU
Alice ->> Alice HS: /send_pdus
Alice HS ->> Bob HS: /send_invite

200 OK Response:

{
    pdu:  PDU
}
POST /_matrix/client/v4/join/{roomIdOrAlias} && POST /_matrix/client/v4/rooms/{roomId}/join

A number of fields are added to the response of the /join endpoints: room_version, via_server, and pdu. These are added to help the client when sending the join event to the /send_pdus endpoint. The via_server is the server chosen by the homeserver to perform the join via. The via_server should be passed along to the /send_pdus endpoint with the fully signed version of this event.

Room joining adds a new cryptoid field to the request body. The cryptoid must be valid Unpadded Base64 and 32 bytes in size in order to be a valid ed25519 public key. This field is used for the homeserver to be able to create the join event on behalf of the client and for the homeserver to validate the user is joining with the correct cryptoID if the join follows an invite event. If this is a join without a matching invite, the homeserver needs to be told which cryptoID to correlate to this room for this user.

Request:

{
    ...,
    cryptoid: string
}

200 OK Response:

{
    room_id: string,
    room_version: string,
    via_server: string,
    pdu:  PDU
}
POST /_matrix/client/v4/rooms/{roomId}/leave

The leave endpoint is extended to return a pdu for the client to sign.

200 OK Response:

{
    pdu:  PDU 
}
PUT /_matrix/client/v4/rooms/{roomId}/send/{eventType}/{txnId} && PUT /_matrix/client/v4/rooms/{roomId}/state/{eventType}/{stateKey}

The /send & /state endpoints are extended to return the pdu in the response for the client to sign.

200 OK Response:

{
    event_id: string,
    pdu:  PDU
}
POST /_matrix/client/v4/keys/upload

A one_time_cryptoids field is added to the /keys/upload endpoint in order to upload new one_time_cryptoids for the purposes of inviting the user to new rooms.

Request:

{
    ...,
    one_time_cryptoids: map[string]OneTimeCryptoID
}

200 OK Response:

{
    ...,
    one_time_cryptoid_counts: map[string]int
}

OneTimeCryptoID:

“algorithm:KeyID”: {
    “key”: ”base64_bytes”
}
GET /_matrix/client/v4/sync

The /sync endpoint will need to be extended to report the one-time cryptoID count. In the response, a one_time_cryptoids_count field is added. This is a mapping of cryptoID algorithm (ie. ed25519) to the count of one_time_cryptoids for that algorithm.

200 OK Response:

{
    ...,
    one_time_cryptoids_count: map[string]int
}

The /sync endpoint also requires an extension of the InvitedRoom parameter to include a one_time_cryptoid field which is the cryptoID that was selected by the users homeserver when creating the invite event. This field is necessary in order to inform the client which cryptoID was used to create the invite event since homeservers translate all cryptoIDs to regular mxids when sending events to the client. Then the client can track this association internally in order to correctly sign future events sent to the room.

200 OK Response (InvitedRoom JSON Object):

{
    invite_state:  InviteState,
    one_time_cryptoid: string
}
POST /_matrix/client/v4/rooms/{roomId}/kick

The kick endpoint is extended to return a pdu for the client to sign.

200 OK Response:

{
    pdu:  PDU 
}

TODO: How to handle external users

POST /_matrix/client/v4/rooms/{roomId}/ban

The ban endpoint is extended to return a pdu for the client to sign.

200 OK Response:

{
    pdu:  PDU 
}

TODO: How to handle external users

POST /_matrix/client/v4/rooms/{roomId}/unban

The unban endpoint is extended to return a pdu for the client to sign.

200 OK Response:

{
    pdu:  PDU 
}
PUT /_matrix/client/v4/rooms/{roomId}/redact/{eventId}/{txnId}

TODO

POST /_matrix/client/v4/rooms/{roomId}/upgrade

TODO

TODO: look into the following:

  • Room directory
  • Peek & unpeek
  • sendToDevice
  • What to do with EDUs?
    • Read_markers
    • Presence
    • VOIP stuff
    • Typing
    • Locations? EDU/State?

Auth Rules

A new room version will be required to account for the modifications to the auth rules.

Invite events no longer require a signature from the invited users homeserver. This signature requirement does not appear to have an obvious benefit and would make invite events overly onerous with the new room invite process.

Redaction Rules

A new room version will be required to account for the modifications to the redaction rules.

The m.room.member event content object allows the mxid_mapping key.

User Attestation (Optional)

To attest that a cryptoID belongs to a specific user, the client master_signing_key could sign the join event containing their generated cryptoID, verifying they are that identity, to prevent a server from spoofing a user joining a new room by having the malicious server generate a cryptoID themselves to create & sign events with.

Linking the cryptoID with the master_signing_key will remove the deniability aspect of messages since you are now cryptographically linking your master_signing_key which is synonymous with a users identity, with each cryptoID.

This extension is effectively what is proposed in MSC3917 - Cryptographic Room Memberships.

An alternative to using the master_signing_key would be to use some other client generated key & include that in the attestation of the cryptoID. A client could choose whether to use different room signing keys per room (the benefit of doing this would be to ensure that knowing a users identity in one room did not lead to knowing that same users identity in another room), or use the same room signing key for all rooms. Then at a later time clients could use some out of band attestation mechanism to “cross-sign” in order to verify the user/s are who they say they are. This has the additional benefit of not needing to enter the users recovery passphrase to provide the attestation as clients could store these room signing keys.

Identity Sharing Between Devices

The cryptoIDs of a user are shared between devices using secret storage similar to the way encryption keys are shared. This leverages server-side key backups for key recovery.

Server-Side Key Backups

TODO: detail this section

Potential Issues

Recovery Passphrase Entry

Requiring the master_signing_key to sign a join event in order to attest a user is who they claim to be would typically require the user to enter their recovery passphrase every time they join a room. This is because clients do not usually store this key. This would lead to a large burden on users and would be best to avoid if at all possible.

Additional Attack Vectors

Clients can modify events prior to signing them and sending them to the server for processing. This can lead to issues if the client were to change something such as the prev_events which could lead to further problems. In order to mitigate this, a server should perform validation of each event being received from the /send_pdus endpoint. A homeserver could do this by storing the hash of an event prior to sending it to a client, then ensure any event received by the /send_pdus endpoint has a matching hash to one stored previously.

A homeserver can run out of one-time cryptoIDs used during invites. Homeservers should protect against this by attempting to detect malicious activity which seeks to deplete the one-time cryptoID reserves for a user. An alternative would be to have a fallback one-time cryptoID. The issue with relying on this mitigation is that it could quickly become the case that a client ends up with the same cryptoID in many rooms. This is not necessarily an issue unless that user wants to keep their cryptoIDs separate in order to maintain the pseudonymity they provide.

Identity/Key Migration

This MSC currently does not account for the possibility of either changing a cryptoID key, or of changing the cryptoID key algorithm. This would potentially involve some manner of distinguishing the cryptoID algorithm in use and of being able to change a user's associated cryptoID key in a room. Both use cases are important in their own right and need further consideration before this MSC can be considered for acceptance.

Alternatives

Clients delegate event signing on a per-event basis

In this alternative, all events would add a field to event content specifying the event signing delegate (such as the user's homeserver). All events would be expected to be signed by this delegate.

Advantages: This has the advantage of avoiding a second round trip for normal messaging.

Disadvantages: This has the disadvantage of the added complexity of trying to protect event content such that only a client is allowed to specify a signing delegate. This ends up leading to a number of issues where homeservers could be able to replay events on a client's behalf, thus minimizing the benefits of cryptographic identities.

This also increases the size of every single event due to the addition of required content fields.

Details

Event Signing

In order to ensure the allowed_signing_keys was actually specified by the client, clients now sign the content section of all events. Events are modified to add nonce, allowed_signing_keys, hash and signatures fields. These fields are used to prevent malicious homeservers from spoofing events on a clients behalf. The signature is created by signing the combination of type, state_key and redacted content. Signing the room_id is not required since cryptoIDs are already unique per room but could be added if necessary. The nonce value should be generated as a random 128-bit integer (or UUIDv4?), encoded as unpadded base64 (or hex for UUID?). It is especially important for the nonce to be unique for each type and state_key pairing in order to ensure events cannot be replayed by a malicious homeserver. The hash is the sha-256 hash, encoded as unpadded base64, of the content fields before signatures have been added (including the new nonce, and allowed_signing_keys fields). The hash is required in order to be able to verify the event signature if the event content is ever redacted.

All events are now required to possess the above mentioned fields inside of content.

An example event would look like:

{
  "msgtype": "m.text",
  "body": "hello world!",
  "m.client_signatures": {
    "nonce": "random_number",
    "allowed_signing_keys": {
      "ed25519":"some+server+key"
    },
    "hash": "hash_of_content_without_signatures",
    "signatures": {
      "ed25519:base64+cryptoid": "signature+of+cryptoid"
    }
  }
}
Auth Rules

Both clients and servers should now check the content signatures to validate whether the type + state_key + redacted content was signed by the sender. Only the redacted content needs to be signed since that will contain a hash of the full contents that can be used to verify the full event. Signing the redacted content instead of just the hash allows for further field validation (ie. fields from m.room.member, m.room.join_rules, and m.room.power_levels to name a few). If a server detects the signature is wrong it should reject the event. If a client detects the signature is wrong it should alert the user who can then decide what further action to take, if any. A client detecting an invalid signature means their homeserver either didnt check the signature or did check the signature and didnt reject the event.

Servers should also check that the full event was signed by one of the keys present in the allowed_signing_keys field, or by the cryptoID itself. If the event was not signed by one of these keys, the server should reject the event. Allowing events to be signed by the cryptoID keeps the possibility of clients to perform state resolution and generate full events if they should choose to do so.

To further validate the event, new rules need to be added to verify the nonce field. Homeservers should ensure that the nonce is unique for events from that user in this room. It is most important to ensure that the nonce value is unique per type and state_key pairing. Duplicate nonce values that are used for different rooms or type/state_key pairings arent an issue since the event signatures protect against replay attacks where these values have been modified. If a homeserver receives an event with a nonce that is identical to another event with the same type and state_key, that event should be rejected. Clients have the option to also perform nonce validation in this way if the possibility of colluding homeservers is suspected. When sending an event via the C-S API, a homeserver should verify that the nonce of the new event is unique or reject the event from the client. If the event is rejected in this way, the homeserver should return a response status of 400 with an errcode of M_DUPLICATE_NONCE.

Problem: How can a client generate a usable nonce?

Problem: How could a homeserver validate a nonce as being unique without requiring them to know the entire room DAG?

Redaction Rules

The following fields should be preserved during redaction for all event types:

  • nonce
  • allowed_signing_key
  • hash
  • signature
Replay Attacks

Clients are now responsible for signing the content field of events but they dont sign the full event. This means that a malicious homeserver could take the contents of an existing room event and replace everything else in the event without anyone knowing. This could include fields such as type, state_key, prev_events, and room_id. In order to minimize the effects of a replay attack, the client should sign the combination of type, state_key, and content. Signing type prevents reusing the contents in an event of another event type. Signing state_key prevents attacks such as changing the membership state of another user. Signing content prevents a malicious homeserver from generating arbitrary content on behalf of a client.

Even with the above mitigation, a malicious homeserver could still replay an event in the same room, with the same content, type and state_key at a different location in the DAG. That is to say, a homeserver can replay the same event with different values for prev_events and auth_events. An example of this could take the form of changing the power levels earlier or later in time.

A further mitigation could be to use the make/send event, double round trip, approach where a client first requests the full event from their homeserver, then signs the full event before sending that into the room. This could be done only for state events since the effects of replay attacks on state events is much more devastating to a room and state events occur infrequently. This would add an additional level of security while keeping the normal event sending flow fast since non-state events wouldnt have the additional client-server round trip.

Clients sign full events via room extremity tracking

In this model the client would be responsible for creating a full event (including prev_events and auth_events) by tracking and resolving the rooms state.

Advantages: This has the advantage of events being fully signed by the cryptoID and avoiding a second round trip.

Disadvantages: This has the disadvantage of requiring clients to do state resolution which cannot reasonably be done by clients due to /sync not returning information in such a way that forward extremities can be properly tracked.

Clients delegate event signing in their m.room.member event

In this model the client would add a allowed_signing_keys field to their m.room.member event in order to delegate event signing to another party. Homeservers still have full authority over a clients events in this scenario since the client doesnt sign any part of each event to verify they are the sender.

Advantages: This has the advantage of not adding additional size to each event.

Disadvantages: This has the disadvantage of giving over full event control to the delegated homeserver. It also has the disadvantage of trying to resolve allowed_signing_keys if a client wants to remove authority from a homeserver or there are conflicts in the room DAG. Revocation of a delegated key is known to be extremely problematic.

Unstable prefix

While this proposal is not considered stable, the org.matrix.msc4080 unstable prefix should be used on all new or changed endpoints.

Stable Unstable
POST /_matrix/client/v1/send_pdus/{txnId} POST /_matrix/client/unstable/org.matrix.msc4080/send_pdus/{txnId}
POST /_matrix/client/v4/createRoom POST /_matrix/client/unstable/org.matrix.msc4080/createRoom
POST /_matrix/client/v4/rooms/{roomId}/invite POST /_matrix/client/unstable/org.matrix.msc4080/rooms/{roomId}/invite
POST /_matrix/client/v4/join/{roomIdOrAlias} POST /_matrix/client/unstable/org.matrix.msc4080/join/{roomIdOrAlias}
POST /_matrix/client/v4/rooms/{roomId}/join POST /_matrix/client/unstable/org.matrix.msc4080/rooms/{roomId}/join
POST /_matrix/client/v4/rooms/{roomId}/leave POST /_matrix/client/unstable/org.matrix.msc4080/rooms/{roomId}/leave
PUT /_matrix/client/v4/rooms/{roomId}/send/{eventType}/{txnId} PUT /_matrix/client/unstable/org.matrix.msc4080/rooms/{roomId}/send/{eventType}/{txnId}
PUT /_matrix/client/v4/rooms/{roomId}/state/{eventType}/{stateKey} PUT /_matrix/client/unstable/org.matrix.msc4080/rooms/{roomId}/state/{eventType}/{stateKey}
POST /_matrix/client/v4/keys/upload POST /_matrix/client/unstable/org.matrix.msc4080/keys/upload
GET /_matrix/client/v4/sync GET /_matrix/client/unstable/org.matrix.msc4080/sync
POST /_matrix/client/v4/rooms/{roomId}/kick POST /_matrix/client/unstable/org.matrix.msc4080/rooms/{roomId}/kick
POST /_matrix/client/v4/rooms/{roomId}/ban POST /_matrix/client/unstable/org.matrix.msc4080/rooms/{roomId}/ban
POST /_matrix/client/v4/rooms/{roomId}/unban POST /_matrix/client/unstable/org.matrix.msc4080/rooms/{roomId}/unban
PUT /_matrix/client/v4/rooms/{roomId}/redact/{eventId}/{txnId} PUT /_matrix/client/unstable/org.matrix.msc4080/rooms/{roomId}/redact/{eventId}/{txnId}
POST /_matrix/client/v4/rooms/{roomId}/upgrade POST /_matrix/client/unstable/org.matrix.msc4080/rooms/{roomId}/upgrade

Dependencies

MSC4014 - Pseudonymous Identities