Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/JAVA/Threema/domain/protocol/src/     Datei vom 25.3.2026 mit Größe 13 kB image not shown  

Quelle  md-d2d-rendezvous.proto   Sprache: unbekannt

 
Spracherkennung für: .proto vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

// ## Connection Rendezvous Protocol
//
// Some mechanisms may request a 1:1 connection between two devices in order to
// transmit data as direct as possible. Establishing such a connection should
// always require user interaction.
//
// The protocol runs an authentication **handshake** on multiple paths
// simultaneously and applies a heuristic to determine the best available path.
// One of the devices is eligible to **nominate** a path after which arbitrary
// encrypted payloads may be exchanged.
//
// ### Terminology
//
// - `RID`: Rendezvous Initiator Device
// - `RRD`: Rendezvous Responder Device
// - `AK`: Authentication Key
// - `ETK`: Ephemeral Transport Key
// - `STK`: Shared Transport Key
// - `PID`: Path ID
// - `RPH`: Rendevous Path Hash
// - `RIDAK`: RID's Authentication Key
// - `RRDAK`: RRD's Authentication Key
// - `RIDTK`: RID's Transport Key
// - `RRDTK`: RRD's Transport Key
// - `RIDSN`: RID's Sequence Number
// - `RRDSN`: RRD's Sequence Number
//
// ### General Information
//
// **Sequence number:** The sequence number starts with `1` and is counted
// separately for each direction (i.e. there is one sequence number counter for
// the client and one for the server). We will use `RIDSN+` and `RRDSN+` in this
// document to denote that the counter should be increased **after** the value
// has been inserted (i.e. semantically equivalent to `x++` in many languages).
//
// **Framing:** An `extra.transport.frame` is being used to frame all
// transmitted data even if the transport supports datagrams. This intentionally
// allows to fragment a frame across multiple datagrams (e.g. useful for limited
// APIs that cannot deliver data in a streamed fashion).
//
// ### Key Derivation
//
//     RIDAK = BLAKE2b(key=AK.secret, salt='rida', personal='3ma-rendezvous')
//     RRDAK = BLAKE2b(key=AK.secret, salt='rrda', personal='3ma-rendezvous')
//
//     STK = BLAKE2b(
//       key=
//           AK.secret
//        || X25519HSalsa20(<local.ETK>.secret, <remote.ETK>.public)
//       salt='st',
//       personal='3ma-rendezvous'
//     )
//
//     RIDTK = BLAKE2b(key=STK.secret, salt='ridt', personal='3ma-rendezvous')
//     RRDTK = BLAKE2b(key=STK.secret, salt='rrdt', personal='3ma-rendezvous')
//
// ### Encryption Schemes
//
// RID's encryption scheme is defined in the following way:
//
//     ChaCha20-Poly1305(
//       key=<RID*K.secret>,
//       nonce=u32-le(PID) || u32-le(RIDSN+) || <4 zero bytes>,
//     )
//
// RRD's encryption scheme is defined in the following way:
//
//     ChaCha20-Poly1305(
//       key=<RRD*K.secret>,
//       nonce=u32-le(PID) || u32-le(RRDSN+) || <4 zero bytes>,
//     )
//
// ### Rendezvous Path Hash Derivation
//
// A Rendezvous Path Hash (RPH) can be used to ensure that both parties are
// connected to each other and not to some other party who was able to intercept
// AK:
//
//     RPH = BLAKE2b(
//       out-length=32,
//       salt='ph',
//       personal='3ma-rendezvous',
//       input=STK.secret,
//     )
//
// ### Path Matrix
//
//     | Name              | Multiple Paths |
//     |-------------------|----------------|
//     | Direct TCP Server | Yes            |
//     | Relayed WebSocket | No             |
//
// ### Protocol Flow
//
// Connection paths are formed by transmitting a `rendezvous.RendezvousInit`
// from RID to RRD as defined in the description of that message.
//
// The connections are then simultaneously established in the background and
// each path must go through the handshake flow with its authentication
// challenges. While doing so, the peers measure the RTT between challenge and
// response in order to determine a good path candidate for nomination.
//
// One of the peers, defined by the upper-layer protocol, nominates one of the
// established paths. Once nominated, both peers close all other paths (WS:
// `1000`).
//
// Once a path has been nominated, that path will be handed to the upper-layer
// protocol for arbitrary data transmission. That data must be protected by
// continuing the respective encryption scheme of the associated role.
//
// ### Handshake Flow
//
// RRD and RID authenticate one another by the following flow:
//
//     RRD ---- Handshake.RrdToRid.Hello ---> RID
//     RRD <- Handshake.RidToRrd.AuthHello -- RID
//     RRD ---- Handshake.RrdToRid.Auth ----> RID
//
// Before the path can be used by the upper-layer protocol, the chosen path must
// be `Nominate`d by either side. The upper-layer protocol must define which
// side may `Nominate`.
//
//     R*D ------- Handshake.Nominate ------> R*D
//
// ### Path Nomination
//
// The following algorithm should be used to determine which path is to be
// nominated. The upper-layer protocol must clearly define whether RRD or RID
// does nomination.
//
// 1. Let `established` be the list of established connection paths.
// 2. Asynchronously, with each connection becoming established, update
//    `established` with the RTT that was measured during the handshake.
// 3. Wait for the first connection path to become established.
// 4. After a brief timeout (or on a specific user interaction), nominate the
//    connection path in the following way, highest priority first:
//    1. Path with the lowest RTT on a mutually unmetered, fast network
//    2. Path with the lowest RTT on a mutually unmetered, slow network
//    3. Path with the lowest RTT on any other network
//
// Note: It is recommended to warn the user if a metered connection path has
// been nominated in case large amounts of data are to be transmitted.
//
// ### WebSocket Close Codes
//
// When WebSocket is used as rendezvous transport, the following close codes
// should be used:
//
// - Normal (`1000`): The rendezvous connection was not nominated or the
//   upper-layer protocol exited successfully.
// - Rendezvous Protocol Error (`4000`): The rendezvous protocol was violated.
//   Possible examples: Invalid WebSocket path, session full. Error details may
//   be included in the WebSocket close _reason_.
// - Init Timeout (`4003`): The other device did not connect in time.
// - Other Device Disconnected (`4004`): The other device disconnected without a
//   reflectable close code.
// - Upper-Layer Protocol Error (`4100`): The rendezvous connection was
//   nominated but an upper-layer protocol error occurred.
//
// The device should log all other close codes but treat them as a _Rendezvous
// Protocol Error_ (`4000`).
//
// Close codes in the `41xx` range as well as `1000` are reflected by the
// rendezvous server to the other device.
//
// ### Security
//
// To prevent phishing attacks, the CORS `Access-Control-Allow-Origin` of any
// WebSocket rendezvous relay server should be set to the bare minimum required
// by the use case.
//
// ### Threat Model
//
// The security of the protocol relies on the security of the secure channel
// where the `RendezvousInit` is being exchanged.
//
// Arbitrary WebSocket URLs and arbitrary IPv4/IPv6 addresses can be provided by
// RID where RRD would connect to. It is therefore required that RRD can trust
// RID to not be malicious.
//
// AK must be exchanged over a sufficiently secure channel. Concretely, AK must
// be sufficiently protected to at least resist a brute-force attack for the
// time between AK being exchanged and the handshake being fulfilled.
//
// A PID must be unique and not be re-used for a specific AK.

syntax = "proto3";

package rendezvous;

option java_package = "ch.threema.protobuf.d2d.rendezvous";

// Contains the data necessary to initialise a 1:1 connection between two
// devices.
//
// When creating this message, run the following sub-steps simultaneously and
// wait for them to finish:
//
// 1. If the device is able to create a TCP server socket:
//    1. Bind to _any_ IP address with a random port number. Silently ignore
//       failures.
//    2. If successful, let `addresses` be the list of available IP addresses on
//       network interfaces the server has been bound to.
//    3. Drop any loopback and duplicate IP addresses from `addresses`.
//    4. Drop link-local IPv6 addresses associated to interfaces that only
//       provide link-local IPv6 addresses.
//    5. Sort `addresses` in the following way, highest priority first:
//         1. IP addresses on unmetered, fast networks
//         2. IP addresses on unmetered, slow networks
//         3. IP addresses on metered, fast networks
//         4. Any other addresses
//    6. Complete the subroutine and provide `addresses` and other necessary
//       data in the `direct_tcp_server` field.
// 2. Connect to a WebSocket relay server:
//    1. Generate a random 32 byte hex-encoded rendezvous path.
//    2. Connect to the WebSocket relay server URL as provided by the context
//       with the generated hex-encoded rendezvous path.
//    3. Once connected, complete the subroutine and provide the necessary data
//       in the `relayed_web_socket` field.
//
// When receiving this message:
//
// 1. If `version` is unsupported, abort these steps.
// 2. If any `path_id` is not unique, abort these steps.
// 3. If the device is able to create a TCP client connection:
//    1. Let `addresses` be the IP addresses of `direct_tcp_server`.
//    2. Filter `addresses` by discarding IPs with unsupported families (e.g. if
//       the device has no IPv6 address, drop any IPv6 addresses).
//    3. For each IP address in `addresses`:
//       1. Connect to the given IP address in the background.
//       2. Wait 100ms.
// 4. Connect to the provided relayed WebSocket server in the background.
// 5. On each successful direct or relayed connection made in the background,
//    forward an event to the upper-layer protocol in order for it to select one
//    of the paths for nomination.
message RendezvousInit {
  enum Version {
    // Initial version.
    V1_0 = 0;
  }
  Version version = 1;

  // 32 byte ephemeral secret Authentication Key (AK).
  bytes ak = 2;

  // Network cost of an interface
  enum NetworkCost {
    // It is unknown whether the interface is metered or unmetered
    UNKNOWN = 0;
    // The interface is unmetered
    UNMETERED = 1;
    // The interface is metered
    METERED = 2;
  }

  // Relayed WebSocket path
  message RelayedWebSocket {
    // Unique Path ID (PID) of the path
    uint32 path_id = 1;

    // Network cost
    NetworkCost network_cost = 2;

    // Full URL to the WebSocket server with a random 32 byte hex-encoded
    // rendezvous path. Must begin with `wss://`.
    string url = 3;
  }
  RelayedWebSocket relayed_web_socket = 3;

  // Direct path to a TCP server created by the initiator
  message DirectTcpServer {
    // Random 16 bit port. Values greater than 65535 are invalid.
    uint32 port = 1;

    // List of associated IP addresses. Each IP address creates its own path.
    repeated IpAddress ip_addresses = 2;

    // An IP address
    message IpAddress {
      // Unique Path ID (PID) of the path
      uint32 path_id = 1;

      // Network cost
      NetworkCost network_cost = 2;

      // IPv4 or IPv6 address
      string ip = 3;
    }
  }
  DirectTcpServer direct_tcp_server = 4;
}

// Messages required for the initial lock-step handshake between RRD and RID.
message Handshake {
  // Handshake messages from RRD to RID.
  message RrdToRid {
    // Initial message from RRD containing its authentication challenge,
    // encrypted by RRD's encryption scheme with RRDAK.
    message Hello {
      // 16 byte random authentication challenge for RID.
      bytes challenge = 1;

      // 32 byte ephemeral public key (`ETK.public`).
      bytes etk = 2;
    }

    // Final message from RRD responding to RID's authentication challenge,
    // encrypted by RRD's encryption scheme with RRDAK.
    //
    // When receiving this message:
    //
    // 1. If the challenge `response` from RRD does not match the challenge sent
    //    by RID, close the connection with a protocol error (WS: `4000`) and
    //    abort these steps.
    message Auth {
      // 16 byte repeated authentication challenge from RRD.
      bytes response = 1;
    }
  }

  // Handshake messages from RID to RRD.
  message RidToRrd {
    // Initial message from RID responding to RRD's authentication challenge and
    // containing RID's authentication challenge, encrypted by RID's encryption
    // scheme with RIDAK.
    //
    // When receiving this message:
    //
    // 1. If the challenge `response` from RID does not match the challenge sent
    //    by RRD, close the connection with a protocol error (WS: `4000`) and
    //    abort these steps.
    message AuthHello {
      // 16 byte repeated authentication challenge from RRD.
      bytes response = 1;

      // 16 byte random authentication challenge for RRD.
      bytes challenge = 2;

      // 32 byte ephemeral public key (`ETK.public`).
      bytes etk = 3;
    }
  }
}

// Nominates the path. The upper-layer protocol defines whether RID or RRD may
// nominate and is encrypted by the respective encryption scheme with RIDTK or
// RRDTK.
//
// When receiving this message:
//
// 1. If the sender was not eligible to `Nominate`, close the connection with a
//    protocol error (WS: `4000`) and abort these steps.
// 2. Close all other pending or established connection paths (WS: `1000`).¹
//
// ¹: Closing other paths is only triggered by the receiver as it may otherwise
//    lead to a race between nomination and close detection.
message Nominate {}

[Dauer der Verarbeitung: 0.23 Sekunden, vorverarbeitet 2026-04-27]