|
|
|
|
Quelle csp-e2e-fs.proto
Sprache: unbekannt
|
|
Spracherkennung für: .proto vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]
// # Forward Security Subprotocol
//
// This protocol specifies forward security for end-to-end encrypted chat server
// messages. It is based on sessions established between two parties, where each
// session has a unique ID and is associated with ephemeral key material
// negotiated between the parties using a key exchange mechanism and a hash
// chain based key derivation.
//
// Each party is either an initiator or a responder in a given session. Once
// established, a session will be used bidirectionally.
//
// Content messages can take any other type that could normally be sent without
// Forward Security, and wrap the message contained within using a separate
// cryptographic layer that provides Forward Security.
//
// ## Terminology
//
// - `CK`: Client Key (permanent secret key associated to the Threema ID)
// - `FS`: Forward Security
// - `SI`: Session Initiator
// - `SR`: Session Responder
// - `2DH`: Unidirectional forward security with two DH calculations
// - `4DH`: Bidirectional forward security with four DH calculations
// - `FSSK`: Forward Security Session Key (ephemeral key pair generated by both
// sides for each session)
// - `2DHK`: 2DH key (current iteration)
// - `4DHK`: 4DH key (current iteration)
// - `XDHK`: 2DH or 4DH key (current iteration)
// - `XDHMK`: 2DH or 4DH message key (current iteration)
//
// ## Modes
//
// Note: This is an informational section. The concrete steps surrounding modes
// are described in the steps for the `Envelope`.
//
// A key negotiation normally needs active participation by both involved
// parties before any actual messages can be exchanged. This is not practical in
// a messaging app, as the other party may not be online at the time when the
// first message is sent.
//
// Thus, the protocol specifies two modes, called 2DH and 4DH which will be
// described briefly in the following sections.
//
// ### 2DH Mode
//
// 2DH mode can be used immediately, even in a new session, as it does not
// involve any additional key material from the responder (i.e. remote party).
// However, it only protects against a compromise of the initiator's permanent
// secret key, not of the responder's permanent secret key. It is still better
// than sending all messages without Forward Security until a bidirectional 4DH
// session has been negotiated.
//
// ### 4DH Mode
//
// A session enters 4DH mode when the initiator has processed the responder's
// `Accept` message and sent an `Encapsulated.Confirm` message back to the
// responder. At this point, both sides have the required key material to derive
// the 4DH root chain key from. Messages sent from this point on are secure even
// in the event of a future compromise of the permanent secret key of either
// party.
//
// ## Keys
//
// Each party uses two different asymmetric X25519 key pairs:
//
// - `CK`: Client Key, the permanent secret key associated to the user's Threema
// ID.
// - `FSSK`: Forward Security Session Key, an ephemeral key pair for the FS
// session.
//
// Each party also learns the remote's `FSSK.public` from the `Init` or `Accept`
// message. The remote's `CK.public` of the peer's Threema ID is assumed to be
// already known.
//
// ## Key Derivation
//
// The two parties maintain the following keys during a session:
//
// - Local 2DHK (initiator only)
// - Remote 2DHK (responder only)
// - Local 4DHK (both parties)
// - Remote 4DHK (both parties)
//
// For each key, the current iteration of the key and a counter (see below) must
// be stored.
//
// The initiator derives the initial keys as follows:
//
// local-2DHK = BLAKE2b(
// input=
// X25519HSalsa20(<local.CK>.secret, <remote.CK>.public)
// || X25519HSalsa20(<local.FSSK>.secret, <remote.CK>.public),
// salt='ke-2dh-<local-threema-id>',
// personal='3ma-e2e',
// )
//
// local-4DHK = BLAKE2b(
// key=BLAKE2b(
// out-length=64,
// input=
// X25519HSalsa20(<local.CK>.secret, <remote.CK>.public)
// || X25519HSalsa20(<local.FSSK>.secret, <remote.CK>.public)
// || X25519HSalsa20(<local.CK>.secret, <remote.FSSK>.public)
// || X25519HSalsa20(<local.FSSK>.secret, <remote.FSSK>.public)
// ),
// salt='ke-4dh-<local-threema-id>',
// personal='3ma-e2e',
// )
//
// remote-4DHK = BLAKE2b(
// key=BLAKE2b(
// out-length=64,
// input=
// X25519HSalsa20(<local.CK>.secret, <remote.CK>.public)
// || X25519HSalsa20(<local.FSSK>.secret, <remote.CK>.public)
// || X25519HSalsa20(<local.CK>.secret, <remote.FSSK>.public)
// || X25519HSalsa20(<local.FSSK>.secret, <remote.FSSK>.public)
// ),
// salt='ke-4dh-<remote-threema-id>',
// personal='3ma-e2e',
// )
//
// The responder calculates the initial keys as follows:
//
// remote-2DHK = BLAKE2b(
// input=
// X25519HSalsa20(<local.CK>.secret, <remote.CK>.public)
// || X25519HSalsa20(<local.CK>.secret, <remote.FSSK>.public),
// salt='ke-2dh-<remote-threema-id>',
// personal='3ma-e2e',
// )
//
// local-4DHK = BLAKE2b(
// key=BLAKE2b(
// out-length=64,
// input=
// X25519HSalsa20(<local.CK>.secret, <remote.CK>.public)
// || X25519HSalsa20(<local.CK>.secret, <remote.FSSK>.public)
// || X25519HSalsa20(<local.FSSK>.secret, <remote.CK>.public)
// || X25519HSalsa20(<local.FSSK>.secret, <remote.FSSK>.public)
// ),
// salt='ke-4dh-<local-threema-id>',
// personal='3ma-e2e',
// )
//
// remote-4DHK = BLAKE2b(
// key=BLAKE2b(
// out-length=64,
// input=
// X25519HSalsa20(<local.CK>.secret, <remote.CK>.public)
// || X25519HSalsa20(<local.CK>.secret, <remote.FSSK>.public)
// || X25519HSalsa20(<local.FSSK>.secret, <remote.CK>.public)
// || X25519HSalsa20(<local.FSSK>.secret, <remote.FSSK>.public)
// ),
// salt='ke-4dh-<remote-threema-id>',
// personal='3ma-e2e',
// )
//
// Each of the `*DHK` keys will be superseded after a message has been encrypted
// in the respective direction as follows:
//
// XDHK' = BLAKE2b(
// key=<local|remote>-XDHK,
// salt='kdf-ck',
// personal='3ma-e2e',
// )
//
// However, actual messages are encrypted by:
//
// XDHMK = BLAKE2b(
// key=<local|remote>-XDHK,
// salt='kdf-aek',
// personal='3ma-e2e',
// )
//
// So, for example, when an outgoing 4DH message is being sent, it will be
// encrypted by deriving `XDHMK` from `local-4DHK` and then `local-4DHK` will be
// replaced by applying `XDKH'` to `local-4DHK`.
//
// ### Key Counters
//
// Note: This is an informational section. The concrete processing steps
// required for keys and key counters are deeply integrated into the processing
// steps of the `Envelope`.
//
// Each local/remote 2DHK/4DHK is associated with a counter, which describes how
// many times the XDHK' has been applied since the initial key derivation.
// Whenever a new message has been sent in a session, the corresponding counter
// must be incremented and the key replaced by applying the XDHK' derivation on
// it. As this operation cannot be reversed, counter values cannot go back.
//
// To account for lost messages (e.g. when the recipient has been offline for an
// extended period of time or when a short-lived message such as
// `typing-indicator` or `call-offer` has been removed from the chat server),
// either party must be prepared to accept counters that have skipped a few
// values, and apply XDHK' as many times as is needed to reach the new counter
// value. To limit the CPU impact, the permissible counter delta is limited to
// 25'000.
//
// Note: A counter delta of 25'000 allows for roughly 100 hours of typing while
// the recipient is offline. Users who exceed that are considered insane and out
// of scope!
//
// ## Session States
//
// This section explains the various possible session states.
//
// Note: This is an informational section. The concrete state creation and
// transformation steps are deeply integrated into the processing steps of the
// `Envelope`.
//
// The _local_ session states are only valid for SI while the _remote_ session
// states are only valid for SR.
//
// A session is considered _bidirectional_ 4DH if the session state is
// `local-out-4dh-in-4dh` (`L44`) or `remote-in-4dh-out-4dh` (`R44`).
//
// ### `local-out-2dh-in-none` (`L20`)
//
// - An `Init` has been sent to remote.
// - Outgoing messages are protected with 2DH.
// - Incoming messages are not allowed by this state.
// - Once an `Accept` has been received for this session, we move into
// `local-out-4dh-in-4dh` (`L44`) state.
//
// ### `local-out-4dh-in-4dh` (`L44`)
//
// - An `Accept` has been received for this session.
// - Outgoing and incoming messages are protected with 4DH.
//
// ### `remote-in-2dh-out-none` (`R20`)
//
// - An `Init` has been received and the session has been created. An `Accept`
// is supposed to be sent with the next outgoing message to remote.
// - Incoming messages are protected with 2DH.
// - Outgoing messages are not allowed by this state.
// - Once the `Accept` has been sent, we move into `remote-in-2dh-out-4dh`
// (`R24`).
//
// ### `remote-in-2dh-out-4dh` (`R24`)
//
// - An `Accept` has been sent to remote.
// - Incoming messages are protected with 2DH.
// - Outgoing messages are protected with 4DH.
// - Once a 4DH encrypted `Encapsulated` message has been received for this
// session, we move into `remote-in-4dh-out-4dh` (`R44`).
//
// ### `remote-in-4dh-out-4dh` (`R44`)
//
// - An incoming 4DH protected `Encapsulated` message has been received for this
// session.
// - Outgoing and incoming messages are protected with 4DH.
//
// ## Protocol Flow
//
// Note: This is an informational section. The concrete flows are deeply
// integrated into the incoming/outgoing message processing steps.
//
// ### Session Establishment
//
// An FS session negotiation is typically started when a user sends the first
// message to a remote Threema ID. The user assumes the role of SI, creates a
// new `L20` session by sending an `Init` message, followed by any number of
// `Encapsulated` messages in 2DH mode.
//
// SI ------------- Init -------------> SR [1]
// SI ------ Encapsulated (2DH) ------> SR [0..N]
//
// Note that `Encapsulated` messages in 2DH mode are limited to the minimum
// version that is announced in the `Init` because the initiator does not know
// yet which version is supported by the responder.
//
// SR will create a corresponding `R20` session. Once an outgoing message
// towards SI is being triggered by SR, it will send an `Accept` and move the
// session into `R24` state, followed by any number of `Encapsulated` messages
// in 4DH mode.
//
// SI <----------- Accept ------------- SR [1]
// SI <----- Encapsulated (4DH) ------- SR [1..N]
//
// When SI receives the `Accept`, it will move the session into `L44` state,
// followed by any number of `Encapsulated` messages in 4DH mode.
//
// SI ------ Encapsulated (4DH) ------> SR [0..N]
//
// With the first `Encapsulated` message received, SR will move the session into
// `R44` state.
//
// At this point, the session has been fully established as a _bidirectional_
// 4DH session and either side may send any number of `Encapsulated` messages in
// 4DH mode.
//
// At any point either party may `Terminate` a session, for example when the
// user is about to delete their account (or FS session information) and wants
// to notify the peer that the key material should be discarded.
//
// If any party receives a `Encapsulated` message that it cannot decrypt (e.g.
// due to having lost the FS key material), it eventually sends a `Reject`
// message to inform the other party that it can no longer use this particular
// FS session, and also tells it the ID of the message that could not be
// decrypted. The sender can then (after manual confirmation by the user)
// re-send the message in a new FS session.
//
// ## Handling Session Loss
//
// A party could lose FS sessions information, e.g. due to reinstallation or
// relocation of the app to a new device. FS sessions are not included in
// backups, as this would conflict with their ephemeralness. When a party
// receives a message that it cannot decrypt due to a missing session, it
// signals this to the other party using a `Reject` message.
//
// ## Race Conditions
//
// It is possible for both parties to create an FS session independently, before
// having received the other party's `Init`. In that case, both parties
// determinstically run a conflict resolve mechanism based on the session ID.
//
// Each party will proceed normally to ensure any messages sent while the race
// is ongoing will be received. When choosing a session for sending a new
// message and there is more than one _bidirectional_ 4DH session available for
// the desired peer, the one with the lowest session ID will be chosen and the
// other one removed. This will ensure that both parties will eventually start
// using the same session in both directions.
//
// ## Notifying the User
//
// To prevent undetected MITM attacks by a third party that has gained access to
// the permanent secret key of either party, the user will be informed whenever
// a new session is negotiated or a non-FS message has been received even though
// an FS session was available.
//
// ## Session Implementation
//
// Note: This is a normative section. The steps will imply that these rules are
// being applied!
//
// Session uniqueness is determined by the following tuple:
//
// - the remote Threema ID,
// - a session ID.
//
// When looking up an FS session, this tuple must always be provided!
//
// An FS session has the following properties:
//
// - a remote Threema ID,
// - a session ID, and
// - a state and all necessary properties associated to that state.
//
// An FS session must be stored in a standalone storage and not as part of the
// contact because FS sessions can be created prior to the contact being
// created.
//
// When an FS session state _transitions_, _moves_ or _advances_, all keys that
// are no longer required must be erased securely.
//
// When an XDHK key iteration is being _replaced_, it must be erased securely.
//
// When an FS session is being _removed_ or _replaced_, it and all associated
// properties must be erased securely.
//
// When a contact is being removed, all associated FS sessions must be erased
// securely as well.
//
// ### State Properties
//
// The following properties, and **only** those, must be associated to the
// state.
//
// `L20` properties:
//
// - `version-range`: Supported version range as announced in the `Init` by
// local (immutable)
// - `local-fssk`: Local FSSK (secret key, immutable)
// - `local-2dhk`: Current key state of the `local-2DHK` for outgoing messages
// (using version `version-range.min`, immutable)
//
// `R20` properties:
//
// - `version-range`: Supported version range as announced in the `Init` by
// remote (immutable)
// - `remote-fssk`: Remote FSSK (public key, immutable)
// - `remote-2dhk`: Current key state of the `remote-2DHK` for incoming messages
// (using version `version-range.min`, mutable)
//
// `R24` properties:
//
// - `remote-2dhv`: Current version used for incoming messages (immutable)
// - `remote-2dhk`: Current key state of the `remote-2DHK` for incoming messages
// still in flight (mutable).
// - `local-4dhv`: Current version used for outgoing messages (immutable)
// - `local-4dhk`: Current key state of the `local-4DHK` for outgoing messages
// (mutable).
// - `remote-4dhv`: Version used for incoming messages in `R44` state
// (immutable)
// - `remote-4dhk`: Key for incoming messages in `R44` state (immutable).
//
// `L44` and `R44` properties:
//
// - `local-4dhv`: Current version used for outgoing messages (mutable).
// - `local-4dhk`: Current key state of the `local-4DHK` for outgoing messages
// (mutable).
// - `remote-4dhv`: Current version used for incoming messages (mutable).
// - `remote-4dhk`: Current key state of the `remote-4DHK` for incoming messages
// (mutable).
//
// ### Key State
//
// A key state contains a key iteration, i.e. a local or remote `XDHK`, and the
// associated counter.
//
// The key state is bound to a specific direction (i.e. _local_ for outgoing
// messages or _remote_ for incoming messages).
//
// The following steps are defined as the _XDHK Incoming Advance Steps_:
//
// 1. Let `target-counter` be the targeted `XDHK` key counter.
// 2. Let `xdhk` be the current `XDHK` iteration of the state.
// 3. Let `distance` be the counter distance between the counter associated to
// `xdhk` and `target-counter`.
// 4. If `distance` is negative or if `distance` is greater than `1000`, log a
// warning and return undefined.
// 5. Loop `distance` times:
// 1. Set `xdhk` to the output of XDHK' applied to `xdhk`.
// 6. Return the tuple of `xdhk` and a context handle that if run, runs the
// _XDHK Replace Steps_ with `xdhk`.
//
// The following steps are defined as the _XDHK Replace Steps_:
//
// 1. Let `xdhk` be the `XDHK` that has just been used (e.g. most likely because
// an incoming message has been processed).
// 2. Apply XDHK' to `xdhk` and replace the current `XDHK` iteration of the
// state with the result of that derivation.
syntax = "proto3";
package csp_e2e_fs;
option java_package = "ch.threema.protobuf.csp.e2e.fs";
option java_multiple_files = true;
import "common.proto";
// A forward security version.
//
// Note: The most significant byte is the major version and the least
// significant byte is the minor version.
//
// IMPORTANT: Don't remove any versions. Apps may serialize and deserialize this
// enum into the database.
enum Version {
// The version is unspecified.
UNSPECIFIED = 0;
// V1.0
//
// - The initial FS release with some backwards compatible adjustments.
// - Encapsulates only the following E2E messages:
// - [`text`](ref:e2e.text)
// - [`location`](ref:e2e.location)
// - [`file`](ref:e2e.file)
// - [`poll-setup`](ref:e2e.poll-setup)
// - [`poll-vote`](ref:e2e.poll-vote)
V1_0 = 0x0100;
// V1.1
//
// - Builds on V1.0 with backwards compatibility.
// - If the remote side offered support for V1.1, all local 1:1 E2E messages
// will be encapsulated.
V1_1 = 0x0101;
// V1.2
//
// - Builds on V1.1 with backwards compatibility.
// - If the remote side offered support for V1.2, all local group E2E messages
// will be encapsulated (i.e. all messages are now encapsulated).
V1_2 = 0x0102;
};
// Forward security version range.
message VersionRange {
// Minimum supported version.
//
// Note: This is the version that will be applied to 2DH message of the
// sender.
uint32 min = 1;
// Maximum supported version.
uint32 max = 2;
}
// A forward security envelope associated to an FS session, containing session
// signalling or an `Encapsulated` message.
//
// When receiving this message with FS unsupported by the client¹:
//
// 1. Let `sender` be the sender of this message.
// 2. If the `content` variant is not `Encapsulated`, discard the message and
// abort these steps.
// 3. Run the steps to send a `Reject` with cause `DISABLED_BY_LOCAL`.
//
// ¹: The steps require basic support for FS (at least decoding the `Envelope`).
// Outdated apps may not provide that in which case case we rely on the feature
// mask correctly excluding FS.
//
// When receiving this message with FS supported by the client:
//
// 1. Let `sender` be the sender of this message.
// 2. Lookup a session with the sender with `envelope.session_id` and let
// `session` be the result.
// 3. If the `content` variant is unknown, discard the message, log a warning
// and abort these steps.
// 4. Invoke the steps associated to receiving the `content` variant. If
// `content` is `Encapsulated`, return the inner message type as
// `inner-type`, the decapsulated message as `inner-message` and the
// `fs-commit-fn` function.
message Envelope {
// Forward security session ID, 16 bytes
bytes session_id = 1;
oneof content {
Init init = 2;
Accept accept = 3;
Reject reject = 4;
Terminate terminate = 5;
Encapsulated encapsulated = 6;
}
}
// Initialises a new session.
//
// When receiving this variant:
//
// 1. Let `session` be the associated FS session or undefined.
// 2. If `session` is defined, log a warning that the `Init` has been repeated,
// discard the message and abort these steps.
// 3. If the `supported_version` range does not include a supported version, log
// a warning, discard the message and abort these steps.
// 4. Ensure that `fssk` contains a valid Curve25519 public key, otherwise log a
// warning, discard the message and abort these steps.
// 5. [...]
// 6. Send an `Accept` and await acknowledgement of the `Accept` message.
// 7. _Acknowledge_ the message that contained this variant.
// 8. Commit the R24 session to storage.
message Init {
// The version range supported by the initiator.
//
// If not provided, assume V1.0 (`0x0100`) for both `min` and `max`.
VersionRange supported_version = 2;
// Ephemeral X25519 public key.
bytes fssk = 1;
}
// Accepts a session.
//
// When receiving this variant:
//
// 1. [...]
// 2. If the `supported_version` range does not include a supported version, log
// a warning, discard the message and abort these steps.
// 3. Ensure that `fssk` contains a valid Curve25519 public key, otherwise log a
// warning, discard the message and abort these steps.
// 4. [...]
// 5. _Acknowledge_ the message that contained this variant.
// 6. Commit the session transition to L44 to storage.
message Accept {
// The version range supported by the responder.
//
// If not provided, assume V1.0 (`0x0100`) for both `min` and `max`.
VersionRange supported_version = 2;
// Ephemeral X25519 public key.
bytes fssk = 1;
}
// Sent when receiving a `Encapsulated` message that cannot be decrypted (e.g.
// because the recipient has lost the session information).
//
// When creating this variant:
//
// 1. Let `encapsulated` be the `Encapsulated` message that triggers this
// reject.
// 2. Set `message_id` to `encapsulated.message_id`.`
// 3. Set `group_identity` to `encapsulated.group_identity`.
//
// When sending this variant:
//
// 1. Let `session` be the associated session (if any).
// 2. Await acknowledgement of this message.
// 3. If `session` is defined, terminate `session` due to `cause` and remove
// `session` from storage.
//
// When receiving this variant:
//
// 1. Let `session` be the associated session (if any).
// 2. If `session` is defined or `cause` is `DISABLED_BY_LOCAL`, schedule a task
// to refresh the feature mask of the sender.
// 3. If `session` is defined, terminate `session` due to `cause` and remove
// `session` from storage.
// 4. If `group_identity` has not been provided:
// 1. Lookup the message for `message_id` in the associated 1:1 conversation
// and let `message` be the result.
// 2. If `message` is not defined or the user is not the sender of `message`,
// abort these steps.
// 3. If the _when rejected_ property associated to `message` allows to
// re-send after confirmation, mark `message` with _re-send requested_.
// 5. If `group_identity` has been provided:
// 1. Run the _Common Group Receive Steps_ and let `group` be the associated
// group to `group_identity`. If the message has been discarded, abort
// these steps.
// 2. Lookup the message for `message_id` in the `group` and let `message` be
// the result.
// 3. If `message` is not defined:
// 1. If the user is the creator of the group, assume that a
// `group-sync-request` has been received from the sender and run the
// associated steps.
// 2. Abort these steps.
// 4. If the user is not the sender of (the original) `message`, abort these
// steps.
// 5. If the _when rejected_ property associated to `message` allows to
// re-send after confirmation, mark `message` with _re-send requested_ and
// add `sender` to the list of group members requesting a re-send for
// `message`.
//
// When the user signals that a message marked with _re-send requested_ should
// be re-sent, run the following steps¹:
//
// 1. Let `message` be the message to be re-sent.
// 2. If `message` is part of a 1:1 conversation:
// 1. Let `receivers` be a list of a single entry with the other party.
// 2. Remove the _re-send requested_ mark on `message`
// 3. If `message` is part of a group conversation:
// 1. Let `receivers` be a copy of the list of group members requesting a
// re-send of `message`.
// 2. Request consent from the user to re-send the message to all `receivers`
// and (asynchronously) wait for the response. If the request was denied,
// abort these steps.
// 3. If `receivers` includes the list of group members requesting a re-send
// of `message`², remove the _re-send requested_ mark on `message` (but
// retain the list of group members requiring a re-send).
// 4. Schedule a persistent task that runs the following steps with `message`
// and `receivers`:
// 1. If `message` has since been removed from the device, discard the task
// and abort these steps.
// 2. Let `receivers` be the provided (snapshot of) receivers that requested
// a re-sent.
// 3. Run the _Common Send Steps_ with `message` and `receivers` (and discard
// the resulting receivers list).
// 4. Remove all `receivers` from the list of receivers requiring a re-send
// for `message`.
//
// The following steps are defined as the _Rejected Messages Refresh Steps_ and
// will be run every time the group members are being updated:
//
// 1. Let `group` be the group conversation.
// 2. If `group` is marked as _left_:
// 1. For each `message` of `group` that has a _re-send requested_ mark,
// remove the mark and the list of receivers requiring a re-send.
// 3. If `group` is not marked as _left_:
// 1. Let `members` be the current list of members for `group`.
// 2. For each `message` of `group` that has a _re-send requested_ mark:
// 1. Let `receivers` be the list of receivers requiring a re-send for
// `message`.
// 2. Remove all entries from `receivers` that are not present in
// `members`.
// 3. If `receivers` is now empty, remove the _re-send requested_ mark on
// `message`.
//
// ¹: The steps ensure that multiple tasks scheduled to re-send the same message
// but with potential different receivers have no ill-effect. In edge cases,
// this may result in sending the same message more than once to a remote party
// which requested a re-send. This is deemed acceptable.
//
// ²: While requesting consent from the user, the list of group members
// requesting a re-send for the message may be asynchronously updated, making
// this check necessary so that a subsequent re-send for the remaining group
// members can be triggered.
message Reject {
// Message ID of the `Encapsulated` message that could not be decrypted and
// that should be sent again in a new session or without a session.
fixed64 message_id = 1;
// The group in which the message has been sent (if any).
common.GroupIdentity group_identity = 3;
// Cause for the reject.
enum Cause {
// General state mismatch. Explicitly includes the following cases:
//
// - The message could not be decrypted.
// - The DH type of the message does not match the expected DH type.
// - An unexpected (major) version has been used.
STATE_MISMATCH = 0;
// No session could be found matching the given Session ID.
UNKNOWN_SESSION = 1;
// The active local feature set does not include support for forward
// security.
DISABLED_BY_LOCAL = 2;
}
Cause cause = 2;
}
// Signals that the sender will not send any further `Encapsulated` messages in
// this session. The recipient should discard all key material related to this
// session.
//
// When sending this variant:
//
// 1. Let `session` be the associated session.
// 2. Await acknowledgement of this message.
// 3. Terminate `session` due to `cause` and remove `session` from storage.
// 4. If `cause` is `UNKNOWN_SESSION` or `RESET`, schedule initiating a new
// session with the receiver.
//
// When receiving this variant:
//
// 1. Let `session` be the associated session (if any).
// 2. If `session` is defined or `cause` is `DISABLED_BY_LOCAL`, schedule a task
// to refresh the feature mask of the sender.
// 3. If `session` is defined, terminate `session` due to `cause` and remove
// `session` from storage.
message Terminate {
// Cause for termination.
enum Cause {
// No session could be found matching the given Session ID.
UNKNOWN_SESSION = 0;
// The session is being reset
RESET = 1;
// The active local feature set does not include support for forward
// security.
DISABLED_BY_LOCAL = 2;
// The remote feature set (fetched from the Directory) does not indicate
// support for forward security.
DISABLED_BY_REMOTE = 3;
}
Cause cause = 1;
}
// A forward security encapsulated CSP E2EE message or a control message.
//
// The following steps are defined as the _FS Refresh Steps_:
//
// 1. Let `contacts` be the provided list of contacts.
// 2. For each `contact` of `contacts`:
// 1. If the `contact` does not support FS, abort these sub-steps.
// 2. Lookup a session with `contact` and let `session` be the result.
// 3. If `session` is undefined, initiate a new `L20` session and set
// `session` to the newly created session. Set `message` to the `Init`
// message for `session` with type `0xa0`.
// 4. If `session` is not a newly created session, create an `Encapsulated`
// message using `session` from inner type `0xfc` (_empty_) and set
// `message` to the encrypted and encoded result with type `0xa0`.
// 5. Send `message` to `contact` and wait for acknowledgement.
// 6. Set `session`'s _updated_ mark to the current timestamp and commit the
// `session` changes to storage.
//
// The following steps are defined as the _FS Encapsulation Steps_:
//
// 1. Let `inner-type` be any message type except `0xa0` and `inner-message` be
// the message data and its nonce. Let `receiver` be the receiver of the
// message.
// 2. If either sender or receiver do not support FS, return `inner-message` and
// a stub `fs-commit-fn` function.
// 3. Let `outer-messages` be an empty list.
// 4. Lookup a session with `receiver` and let `session` be the result.
// 5. If `session` is undefined, initiate a new `L20` session and set `session`
// to the newly created session. Append the `Init` message for `session` to
// `outer-messages` with type `0xa0` and a random nonce.
// 6. If `inner-type` is not eligible to be encapsulated by `session`:
// 1. If `session` is not a newly created session and the `session`'s
// _updated_ mark is older than 24h ago, create an `Encapsulated` message
// using `session` from inner type `0xfc` (_empty_) and append the
// encrypted and encoded result to `outer-messages` with type `0xa0` and a
// random nonce.
// 2. Append `inner-message` as a non-encapsulated message to
// `outer-messages` with type `inner-type` and the nonce of
// `inner-message`.
// 7. If `inner-type` is eligible to be encapsulated by `session`, create an
// `Encapsulated` message using `session` from `inner-type` and
// `inner-message` and append the encrypted and encoded result to
// `outer-messages` with type `0xa0` and the nonce of `inner-message`.
// 8. Return `outer-messages` and an `fs-commit-fn` function that sets the
// `session`'s _updated_ mark to the current timestamp and commits the
// `session` changes to storage when called.
//
// When receiving this variant:
//
// 1. Let `session` be the associated session (if any).
// 2. If `session` is not defined, `Reject` the message with `UNKNOWN_SESSION`
// and abort these steps.
// 3. [...]
// 4. If `offered_version` is `0`, set it to V1.0.
// 5. If `applied_version` is `0`, set it to `offered_version`.
// 6. If `applied_version` is > `offered_version`, `Reject` the `session` and
// the message with a `STATE_MISMATCH` and abort these steps.
// 7. If `session.state.dh_type` is `TWODH`:
// 1. If `session.state` is not `R20` and not `R24`, `Reject` the `session`
// and the message with a `STATE_MISMATCH` and abort these steps.
// 2. If `session.state` is `R20`, let `expected-version` be
// `session.state.version-range.min`.
// 3. If `session.state` is `R24`, let `expected-version` be
// `session.state.remote-2dhv`.
// 4. If `offered_version` and `applied_version` are not equal to
// `expected-version`, `Reject` the `session` and the message with a
// `STATE_MISMATCH`, and abort these steps.
// 8. If `session.state.dh_type` is `FOURDH`:
// 1. If `session.state` is not any of `L44`, `R44` or `R24`, `Reject` the
// `session` and the message with a `STATE_MISMATCH` and abort these
// steps.¹
// 2. Let `pending-versions` be a set of local and remote versions to be
// filled by the following steps.
// 3. If `offered_version` has a different major version, or is lower than
// `session.state.local-4dhv`, `Reject` the `session` and the message with
// a `STATE_MISMATCH`, and abort these steps.
// 4. If `offered_version` has a higher minor version than
// `session.state.local-4dhv`, let `commonly-supported-version` be the
// maximum commonly supported version with `offered_version`.
// 5. If `applied_version` has a different major version, or is lower than
// `session.state.remote-4dhv`, or is not a supported version, `Reject`
// the `session` and the message with a `STATE_MISMATCH` and abort these
// steps.
// 6. If `applied_version` has a higher minor version than
// `session.state.remote-4dhv`, set `pending-versions.remote-4dhv` to
// `applied_version`.
// 7. If `commonly-supported-version` is defined and different to
// `pending-versions.local-4dhv`, update it with that value, create an
// `Encapsulated` message using `session` from inner type `0xfc`
// (_empty_), send the encrypted and encoded result with type `0xa0` to
// the sender and wait for acknowledgement of that message.¹
// 8. Update `session.state` with `pending-versions`.
// 9. Return an `fs-commit-fn` function that sets the `session`'s _updated_ mark
// to the current timestamp and commits the `session` changes to storage when
// called.
//
// ¹: This an exception to the silent ping rule which ensures that FS sessions
// are updated to use the highest available version ASAP.
message Encapsulated {
// Encryption scheme applied to the encapsulated message.
enum DHType {
// The message is encrypted with the 2DH encryption scheme.
TWODH = 0;
// The message is encrypted with the 4DH encryption scheme.
FOURDH = 1;
}
DHType dh_type = 1;
// A monotonically increasing counter, starting at 1 for the first 2DH or 4DH
// `Encapsulated` message sent in this session, and incrementing by 1 for each
// successive `Encapsulated` message.
//
// - Counters for 2DH and 4DH are separate, as they are based on different
// root keys.
// - Counters for each direction are separate.
// - Can be used by the recipient as a hint of how many times to
// rotate/ratchet the KDF, in case an intermediate `Encapsulated` message
// went missing.
uint64 counter = 2;
// The major negotiated version with the _offered_ minor version.
uint32 offered_version = 4;
// The major negotiated version with the _applied_ minor version.
uint32 applied_version = 5;
// The group in which the message has been sent.
//
// Required if the inner message type's _kind_ property is _Group_. Must not
// be set for _1:1_.
common.GroupIdentity group_identity = 6;
// A message as defined by `e2e.container` (but **without** PKCS#7 padding),
// encrypted by:
//
// ```text
// XSalsa20-Poly1305(
// key=XDHMK,
// nonce=00..00,
// data=<e2e.container>,
// )
// ```
bytes encrypted_inner = 3;
}
[Dauer der Verarbeitung: 0.22 Sekunden, vorverarbeitet 2026-04-27]
|
2026-05-26
|
|
|
|
|