docs: update protocol (#1705)

This commit is contained in:
Evgeny
2026-01-27 21:21:54 +00:00
committed by GitHub
parent ac825b0ff3
commit 84e8b72ca3
3 changed files with 522 additions and 55 deletions

View File

@@ -1,4 +1,4 @@
Version 5, 2024-06-22
Version 7, 2025-01-24
# SMP agent protocol - duplex communication over SMP protocol
@@ -9,6 +9,7 @@ Version 5, 2024-06-22
- [SMP servers management](#smp-servers-management)
- [SMP agent protocol scope](#smp-agent-protocol-scope)
- [Duplex connection procedure](#duplex-connection-procedure)
- [Fast duplex connection procedure](#fast-duplex-connection-procedure)
- [Contact addresses](#contact-addresses)
- [Communication between SMP agents](#communication-between-smp-agents)
- [Message syntax](#messages-between-smp-agents)
@@ -20,6 +21,14 @@ Version 5, 2024-06-22
- [Rotating messaging queue](#rotating-messaging-queue)
- [End-to-end encryption](#end-to-end-encryption)
- [Connection link: 1-time invitation and contact address](#connection-link-1-time-invitation-and-contact-address)
- [Full connection link syntax](#full-connection-link-syntax)
- [Short connection link syntax](#short-connection-link-syntax)
- [Short links](#short-links)
- [Short link structure](#short-link-structure)
- [Link key derivation](#link-key-derivation)
- [Link data encryption](#link-data-encryption)
- [Short link resolution](#short-link-resolution)
- [Link data management](#link-data-management)
- [Appendix A: SMP agent API](#smp-agent-api)
- [API functions](#api-functions)
- [API events](#api-events)
@@ -37,6 +46,16 @@ It provides:
SMP agent API provides no security between the agent and the client - it is assumed that the agent is executed in the trusted and secure environment, via the agent library, when the agent logic is included directly into the client application - [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) uses this approach.
This document describes SMP agent protocol version 7. The version history:
- v1: initial version
- v2: duplex handshake - allows including reply queue(s) in the initial confirmation
- v3: ratchet sync - supports re-negotiating double ratchet encryption
- v4: delivery receipts - supports acknowledging message delivery to the sender
- v5: post-quantum - supports post-quantum key exchange in double ratchet (PQDR)
- v6: sender auth key - supports sender authentication key in confirmations
- v7: ratchet on confirmation - initializes double ratchet during confirmation
## SMP agent
SMP agents communicate with each other via SMP servers using [simplex messaging protocol (SMP)](./simplex-messaging.md) according to the API calls used by the client applications. This protocol is a middle layer in SimpleX protocols (above SMP protocol but below any application level protocol) - it is intended to be used by client-side applications that need secure asynchronous bi-directional communication channels ("connections").
@@ -152,7 +171,7 @@ These messages are encrypted with per-queue shared secret using NaCL crypto_box
- `agentConfirmation` - used when confirming SMP queues, contains connection information encrypted with double ratchet. This envelope can only contain `agentConnInfo` or `agentConnInfoReply` encrypted with double ratchet.
- `agentMsgEnvelope` - contains different agent messages encrypted with double ratchet, as defined in `agentMessage`.
- `agentInvitation` - sent to SMP queue that is used as contact address, does not use double ratchet.
- `agentRatchetKey` - used to re-negotiate double ratchet encryption - can contain additional information in `agentRatchetKey`.
- `agentRatchetKey` - used to re-negotiate double ratchet encryption - can contain additional information in `agentRatchetInfo`.
```abnf
decryptedSMPClientMessage = agentConfirmation / agentMsgEnvelope / agentInvitation / agentRatchetKey
@@ -182,7 +201,7 @@ Decrypted SMP message client body can be one of 4 types:
- `agentMessage` - all other agent messages.
`agentMessage` contains these parts:
- `agentMsgHeader` - agent message header that contains sequential agent message ID for a particular SMP queue, agent timestamp (ISO8601) and the hash of the previous message.
- `agentMsgHeader` - agent message header that contains sequential agent message ID for a particular SMP queue and the hash of the previous message.
- `aMessage` - a command/message to the other SMP agent:
- to confirm the connection (`HELLO`).
- to send and to confirm reception of user messages (`A_MSG`, `A_RCVD`).
@@ -200,7 +219,9 @@ decryptedAgentMessage = agentConnInfo / agentConnInfoReply / agentRatchetInfo /
agentConnInfo = %s"I" connInfo
connInfo = *OCTET
agentConnInfoReply = %s"D" smpQueues connInfo
smpQueues = length 1*newQueueInfo ; NonEmpty list of reply queues
agentRatchetInfo = %s"R" ratchetInfo
ratchetInfo = *OCTET
agentMessage = %s"M" agentMsgHeader aMessage msgPadding
agentMsgHeader = agentMsgId prevMsgHash
@@ -215,8 +236,10 @@ HELLO = %s"H"
A_MSG = %s"M" userMsgBody
userMsgBody = *OCTET
A_RCVD = %s"V" msgReceipt
A_RCVD = %s"V" msgReceipts
msgReceipts = length 1*msgReceipt ; NonEmpty list
msgReceipt = agentMsgId msgHash rcptLength rcptInfo
msgHash = shortString
EREADY = %s"E" agentMsgId
@@ -224,14 +247,14 @@ A_QCONT = %s"QC" sndQueueAddr
QADD = %s"QA" sndQueues
sndQueues = length 1*(newQueueUri replacedSndQueue)
newQueueUri = clientVRange smpServer senderId dhPublicKey [sndSecure]
newQueueUri = clientVRange smpServer senderId dhPublicKey [queueMode]
dhPublicKey = length x509encoded
sndSecure = "T"
queueMode = %s"M" / %s"C" ; M - messaging (sender can secure), C - contact
replacedSndQueue = "0" / "1" sndQueueAddr
QKEY = %s"QK" sndQueueKeys
sndQueueKeys = length 1*(newQueueInfo senderKey)
newQueueInfo = version smpServer senderId dhPublicKey [sndSecure]
newQueueInfo = version smpServer senderId dhPublicKey [queueMode]
senderKey = length x509encoded
QUSE = %s"QU" sndQueuesReady
@@ -270,7 +293,7 @@ This is the agent envelope used to send client messages once the connection is e
#### A_RCVD message
This message is sent to confirm the client message reception. It includes received message number and message hash.
This message is sent to confirm the client message reception. It includes a list of message receipts, each containing the received message number, message hash and receipt info.
#### EREADY message
@@ -345,24 +368,22 @@ To summarize, the upgrade to DH+KEM secret happens in a sent message that has PQ
Connection links are generated by SMP agent in response to `createConnection` api call, used by another party user with `joinConnection` api, and then another connection link is sent by the agent in `agentConnInfoReply` and used by the first party agent to connect to the reply queue (the second part of the process is invisible to the users).
Connection link syntax:
### Full connection link syntax
```
connectionLink = connectionScheme "/" connLinkType "#/?smp=" smpQueues "&e2e=" e2eEncryption
connectionLink = connectionScheme "/" connLinkType "#/?v=" versionRange "&smp=" smpQueues ["&e2e=" e2eEncryption] ["&data=" clientData]
connLinkType = %s"invitation" / %s"contact"
connectionScheme = (%s"https://" clientAppServer) | %s"simplex:"
clientAppServer = hostname [ ":" port ]
; client app server, e.g. simplex.chat
e2eEncryption = encryptionScheme ":" publicKey
encryptionScheme = %s"rsa" ; end-to-end encryption and key exchange protocols,
; the current hybrid encryption scheme (RSA-OAEP/AES-256-GCM-SHA256)
; will be replaced with double ratchet protocol and DH key exchange.
publicKey = <base64url X509 SPKI key encoding>
smpQueues = smpQueue [ "," 1*smpQueue ] ; SMP queues for the connection
versionRange = 1*DIGIT / 1*DIGIT "-" 1*DIGIT ; agent version range
e2eEncryption = <e2e encryption parameters for double ratchet>
smpQueues = smpQueue *(";" smpQueue) ; SMP queues for the connection (semicolon-separated)
smpQueue = <URL-encoded queueURI defined in SMP protocol>
clientData = <URL-encoded application-specific data>
```
All parameters are passed via URI hash to avoid sending them to the server (in case "https" scheme is used) - they can be used by the client-side code and processed by the client application. Parameters `smp` and `e2e` can be present in any order, any unknown additional parameters SHOULD be ignored.
All parameters are passed via URI hash to avoid sending them to the server (in case "https" scheme is used) - they can be used by the client-side code and processed by the client application. Parameters can be present in any order, any unknown additional parameters SHOULD be ignored.
`clientAppServer` is not an SMP server - it is a server that shows the instruction on how to download the client app that will connect using this connection link. This server can also host a mobile or desktop app manifest so that this link is opened directly in the app if it is installed on the device.
@@ -370,6 +391,123 @@ All parameters are passed via URI hash to avoid sending them to the server (in c
See SMP protocol [out-of-band messages](./simplex-messaging.md#out-of-band-messages) for syntax of `queueURI`.
### Short connection link syntax
Short links provide a more compact representation by storing connection data on the server:
```
shortLink = shortLinkScheme "/" linkType "#" [linkId "/"] linkKey ["?" shortLinkParams]
shortLinkScheme = %s"simplex:" / (%s"https://" serverHost)
linkType = %s"i" / contactType ; i - invitation, or contact type
contactType = %s"a" / %s"c" / %s"g" / %s"r" ; a - contact, c - channel, g - group, r - relay
linkId = base64url ; only for invitation links
linkKey = base64url ; SHA3-256 hash of fixed data, used to decrypt link data
shortLinkParams = hostParam ["&" portParam] ["&" keyHashParam]
hostParam = %s"h=" hostList
hostList = host *("," host)
portParam = %s"p=" port
keyHashParam = %s"c=" base64url ; server certificate fingerprint
```
Contact types:
- `a` (CCTContact) - direct contact connection
- `c` (CCTChannel) - channel connection
- `g` (CCTGroup) - group connection
- `r` (CCTRelay) - relay connection
Short links can use either the `simplex:` scheme or `https://` with a server hostname. When using the simplex scheme, server information is included in query parameters.
## Short links
Short links provide a compact representation of connection links by storing encrypted connection data on the SMP server. The link key in the URI fragment (after `#`) is never sent to the server, ensuring the server cannot decrypt the stored connection data.
### Link key derivation
The link key is derived from the fixed link data using SHA3-256 hash function:
```
linkKey = SHA3-256(fixedLinkData)
```
The fixed link data includes:
- Agent version range
- Root public key (Ed25519) for signing
- SMP queue connection request (server, queue IDs, encryption keys)
- Optional link entity ID
For contact links, the link ID and encryption key are derived from the link key using HKDF:
```
(linkId, encryptionKey) = HKDF(info="SimpleXContactLink", key=linkKey, outputLen=56)
; linkId = first 24 bytes, encryptionKey = remaining 32 bytes
```
For invitation links, the link ID is stored separately (usually included in the URI), and only the encryption key is derived:
```
encryptionKey = HKDF(info="SimpleXInvLink", key=linkKey, outputLen=32)
```
### Link data encryption
Link data stored on the server consists of two encrypted parts: fixed data and user data. Both are encrypted using NaCl secret_box (XSalsa20-Poly1305) with the derived encryption key:
```abnf
queueLinkData = encFixedData encUserData
encFixedData = largeString ; encrypted padded(signedFixedData, 2008)
encUserData = largeString ; encrypted padded(signedUserData, 13784)
signedFixedData = signature fixedData
signedUserData = signature userData
signature = length 64*64 OCTET ; Ed25519 signature
fixedData = agentVersionRange rootKey linkConnReq [linkEntityId]
agentVersionRange = version version ; min and max agent protocol version
version = 2*2 OCTET
rootKey = length x509encoded ; Ed25519 public key
linkEntityId = shortString
userData = invitationLinkData / contactLinkData
invitationLinkData = %s"I" agentVersionRange connInfo
contactLinkData = %s"C" agentVersionRange userContactData
largeString = 2*2 OCTET *OCTET ; Word16 length prefix
length = 1*1 OCTET
shortString = length *OCTET
```
The fixed data is signed with the root key and its hash becomes the link key. The user data is signed either with the root key (for invitations) or with an owner key (for contact addresses).
### Short link resolution
When a user receives a short link, the agent resolves it as follows:
1. Extract the link key from the URI fragment
2. Send `LGET` command to the SMP server with the link ID
3. Receive encrypted link data from the server
4. Decrypt the link data using the link key
5. Extract the full connection information (SMP queue URI, encryption keys, profile)
6. Proceed with the standard connection procedure using `joinConnection`
For invitation links, the `LKEY` command is used to set the sender key when getting link data. Repeated `LKEY` would require using the same key.
### Link data management
The recipient who created the queue can manage the short link data:
- **LSET** - Set or update the link data associated with a queue. This is used when creating a short link or updating the user data (e.g., profile changes).
- **LDEL** - Delete the link data from the server. This effectively invalidates the short link.
Short links support different connection modes:
- **invitation** - One-time invitation links that can only be used once
- **contact** - Reusable contact address links that can be used multiple times
For contact addresses, the link data includes additional information about the contact type:
- **contact** - Direct contact connection
- **channel** - Channel connection
- **group** - Group connection
- **relay** - Relay connection
The agent maintains the link data and updates it when connection parameters change, ensuring short links remain valid and reflect current connection information.
## Appendix A: SMP agent API
The exact specification of agent library API and of the events that the agent sends to the client application is out of scope of the protocol specification.

View File

@@ -1,4 +1,4 @@
Version 9, 2024-06-22
Version 19, 2025-01-24
# Simplex Messaging Protocol (SMP)
@@ -18,6 +18,10 @@ Version 9, 2024-06-22
- [Simplex queue IDs](#simplex-queue-ids)
- [Server security requirements](#server-security-requirements)
- [Message delivery notifications](#message-delivery-notifications)
- [Client services](#client-services)
- [Service roles](#service-roles)
- [Service certificates](#service-certificates)
- [Service subscriptions](#service-subscriptions)
- [SMP Transmission and transport block structure](#smp-transmission-and-transport-block-structure)
- [SMP commands](#smp-commands)
- [Correlating responses with commands](#correlating-responses-with-commands)
@@ -26,7 +30,11 @@ Version 9, 2024-06-22
- [Recipient commands](#recipient-commands)
- [Create queue command](#create-queue-command)
- [Subscribe to queue](#subscribe-to-queue)
- [Subscribe to multiple queues](#subscribe-to-multiple-queues)
- [Secure queue by recipient](#secure-queue-by-recipient)
- [Set queue recipient keys](#set-queue-recipient-keys)
- [Set short link](#set-short-link)
- [Delete short link](#delete-short-link)
- [Enable notifications command](#enable-notifications-command)
- [Disable notifications command](#disable-notifications-command)
- [Get message command](#get-message-command)
@@ -41,12 +49,22 @@ Version 9, 2024-06-22
- [Request proxied session](#request-proxied-session)
- [Send command via proxy](#send-command-via-proxy)
- [Forward command to destination server](#forward-command-to-destination-server)
- [Short link commands](#short-link-commands)
- [Set link key](#set-link-key)
- [Get link data](#get-link-data)
- [Notifier commands](#notifier-commands)
- [Subscribe to queue notifications](#subscribe-to-queue-notifications)
- [Subscribe to multiple queue notifications](#subscribe-to-multiple-queue-notifications)
- [Server messages](#server-messages)
- [Link response](#link-response)
- [Queue subscription response](#queue-subscription-response)
- [Service subscription response](#service-subscription-response)
- [All service messages received](#all-service-messages-received)
- [Deliver queue message](#deliver-queue-message)
- [Deliver message notification](#deliver-message-notification)
- [Subscription END notification](#subscription-end-notification)
- [Service subscription END notification](#service-subscription-end-notification)
- [Queue deleted notification](#queue-deleted-notification)
- [Error responses](#error-responses)
- [OK response](#ok-response)
- [Transport connection with the SMP server](#transport-connection-with-the-SMP-server)
@@ -65,7 +83,26 @@ It's designed with the focus on communication security and integrity, under the
It is designed as a low level protocol for other application protocols to solve the problem of secure and private message transmission, making [MITM attack][1] very difficult at any part of the message transmission system.
This document describes SMP protocol versions 6 and 7, the previous versions are discontinued.
This document describes SMP protocol version 19. Versions 1-5 are discontinued. The version history:
- v1: binary protocol encoding
- v2: message flags (used to control notifications)
- v3: encrypt message timestamp and flags together with the body when delivered to recipient
- v4: support command batching
- v5: basic auth for SMP servers
- v6: allow creating queues without subscribing (current minimum version)
- v7: support authenticated encryption to verify senders' commands
- v8: SMP proxy for sender commands (PRXY, PFWD, RFWD, PKEY, PRES, RRES)
- v9: faster handshake with SKEY command for sender to secure queue
- v10: DELD event to subscriber when queue is deleted via another connection
- v11: additional encryption of transport blocks with forward secrecy
- v12: BLOCKED error for blocked queues
- v14: proxyServer handshake property to disable transport encryption between server and proxy
- v15: short links with associated data passed in NEW or LSET command
- v16: service certificates
- v17: create notification credentials with NEW command
- v18: support client notices in BLOCKED error
- v19: service subscriptions to messages (SUBS, SOKS, ENDS commands)
## Introduction
@@ -395,13 +432,52 @@ To protect the privacy of the recipients, there are several commands in SMP prot
The clients can optionally instruct a dedicated push notification server to subscribe to notifications and deliver push notifications to the device, which can then retrieve the messages in the background and send local notifications to the user - this is out of scope of SMP protocol. The commands that SMP protocol provides to allow it:
- `enableNotifications` (`"NKEY"`) with `notifierId` (`"NID"`) response - see [Enable notifications command](#enable-notifications-command).
- `enableNotifications` (`"NKEY"`) with `notifierIdResp` (`"NID"`) response - see [Enable notifications command](#enable-notifications-command).
- `disableNotifications` (`"NDEL"`) - see [Disable notifications command](#disable-notifications-command).
- `subscribeNotifications` (`"NSUB"`) - see [Subscribe to queue notifications](#subscribe-to-queue-notifications).
- `messageNotification` (`"NMSG"`) - see [Deliver message notification](#deliver-message-notification).
[`SEND` command](#send-message) includes the notification flag to instruct SMP server whether to send the notification - this flag is forwarded to the recipient inside encrypted envelope, together with the timestamp and the message body, so even if TLS is compromised this flag cannot be used for traffic correlation.
## Client services
SMP protocol supports client services - high capacity clients that act as services. Client services allow scalable message and notification delivery services.
### Service roles
A client service can have one of two roles:
- **Messaging** - Message receiver service that subscribes to and receives messages from multiple SMP queues with a single command.
- **Notifications** - Notification service that subscribes to queue notifications and delivers push notifications to user devices.
Service role is identified in the transport handshake and determines what commands the service is authorized to send.
### Service certificates
To send service commands, services should authenticate themselves to SMP servers using service certificates. This provides:
- **Service identity** - The server assigns a unique service ID based on the service certificate, allowing associating multiple SMP queues with a service.
- **Subscription management** - Services can efficiently manage subscriptions across reconnections without re-subscribing to individual queues.
- **Rate limiting** - Servers can apply rate limits per service identity rather than per connection.
Service certificates are included in the client handshake and verified by the server. The service receives a service ID in the handshake response, which is then used as entity ID in service transmissions.
```abnf
clientHandshakeService = serviceRole serviceCertKey
serviceRole = %s"M" / %s"N" ; Messaging / Notifier
serviceCertKey = certChainPubKey
```
### Service subscriptions
Services use batch subscription commands to subscribe to multiple queues:
- **SUBS** - Subscribe to messages from all associated SMP queues at once. The service provides a count and hash of queue IDs, and receives `SOKS` response with the service ID.
- **NSUBS** - Subscribe to notifications from all associated SMP queues. Similar to SUBS.
- **SOKS** - Server response confirming batch subscription success.
- **ENDS** - Server notification when batch subscriptions are terminated (e.g., when another instance of service connects).
## SMP Transmission and transport block structure
Each transport block has a fixed size of 16384 bytes for traffic uniformity.
@@ -455,15 +531,19 @@ Commands syntax below is provided using [ABNF][8] with [case-sensitive strings e
```abnf
smpCommand = ping / recipientCmd / senderCommand /
proxyCommand / subscribeNotifications / serverMsg
recipientCmd = create / subscribe / rcvSecure /
proxyCommand / notifierCommand / linkCommand / serverMsg
recipientCmd = create / subscribe / subscribeMultiple / rcvSecure / recipientKeys /
enableNotifications / disableNotifications / getMessage
acknowledge / suspend / delete / getQueueInfo
acknowledge / suspend / delete / getQueueInfo / setShortLink / deleteShortLink
senderCommand = send / sndSecure
proxyCommand = proxySession / proxyCommand / relayCommand
serverMsg = queueIds / message / notifierId / messageNotification /
proxySessionKey / proxyResponse / relayResponse
unsubscribed / queueInfo/ ok / error
linkCommand = setLinkKey / getLinkData
proxyCommand = proxySession / proxyForward / relayForward
notifierCommand = subscribeNotifications / subscribeNotificationsMultiple
serverMsg = queueIds / linkResponse / serviceOk / serviceOkMultiple /
message / allReceived / notifierIdResp / messageNotification /
proxySessionKey / proxyResponse / relayResponse /
unsubscribed / serviceUnsubscribed / deleted /
queueInfo / ok / error / pong
```
The syntax of specific commands and responses is defined below.
@@ -480,13 +560,14 @@ SMP servers must verify all transmissions (excluding `ping` and initial `send` c
### Keep-alive command
To keep the transport connection alive and to generate noise traffic the clients should use `ping` command to which the server responds with `ok` response. This command should be sent unsigned and without queue ID.
To keep the transport connection alive and to generate noise traffic the clients should use `ping` command to which the server responds with `pong` response. This command should be sent unsigned and without queue ID.
```abnf
ping = %s"PING"
pong = %s"PONG"
```
This command is always send unsigned.
This command is always sent unsigned.
### Recipient commands
@@ -501,30 +582,54 @@ Servers SHOULD support basic auth with this command, to allow only server owners
The syntax is:
```abnf
create = %s"NEW " recipientAuthPublicKey recipientDhPublicKey basicAuth subscribe sndSecure
create = %s"NEW " recipientAuthPublicKey recipientDhPublicKey optBasicAuth subscribeMode optQueueReqData optNtfCreds
recipientAuthPublicKey = length x509encoded
; the recipient's Ed25519 or X25519 public key to verify commands for this queue
recipientDhPublicKey = length x509encoded
; the recipient's Curve25519 key for DH exchange to derive the secret
; that the server will use to encrypt delivered message bodies
; using [NaCl crypto_box][16] encryption scheme (curve25519xsalsa20poly1305).
basicAuth = "0" / "1" shortString ; server password
optBasicAuth = %s"0" / (%s"1" shortString) ; optional server password
subscribeMode = %s"S" / %s"C" ; S - create and subscribe, C - only create
sndSecure = %s"T" / %s"F" ; T - sender can secure the queue, from v9
optQueueReqData = %s"0" / (%s"1" queueReqData) ; optional queue request data
queueReqData = queueReqMessaging / queueReqContact
queueReqMessaging = %s"M" optMessagingLinkData
queueReqContact = %s"C" optContactLinkData
optMessagingLinkData = %s"0" / (%s"1" senderId encFixedData encUserData)
optContactLinkData = %s"0" / (%s"1" linkId senderId encFixedData encUserData)
senderId = shortString ; first 24 bytes of SHA3-384(corrId)
linkId = shortString
encFixedData = largeString ; encrypted fixed link data
encUserData = largeString ; encrypted user data
optNtfCreds = %s"0" / (%s"1" ntfKey ntfDhKey) ; optional notification credentials
ntfKey = length x509encoded
ntfDhKey = length x509encoded
x509encoded = <binary X509 key encoding>
shortString = length *OCTET
largeString = length2 *OCTET
length = 1*1 OCTET
length2 = 2*2 OCTET ; Word16, network byte order
```
If the queue is created successfully, the server must send `queueIds` response with the recipient's and sender's queue IDs and public key to encrypt delivered message bodies:
```abnf
queueIds = %s"IDS " recipientId senderId srvDhPublicKey sndSecure
serverDhPublicKey = length x509encoded
queueIds = %s"IDS " recipientId senderId srvDhPublicKey optQueueMode optLinkId optServiceId optServerNtfCreds
srvDhPublicKey = length x509encoded
; the server's Curve25519 key for DH exchange to derive the secret
; that the server will use to encrypt delivered message bodies to the recipient
recipientId = shortString ; 16-24 bytes
senderId = shortString ; 16-24 bytes
optQueueMode = %s"0" / (%s"1" queueMode)
queueMode = %s"M" / %s"C" ; M - messaging (sender can secure), C - contact
optLinkId = %s"0" / (%s"1" linkId)
linkId = shortString
optServiceId = %s"0" / (%s"1" serviceId)
serviceId = shortString
optServerNtfCreds = %s"0" / (%s"1" srvNtfId srvNtfDhKey)
srvNtfId = shortString
srvNtfDhKey = length x509encoded
```
Once the queue is created, depending on `subscribeMode` parameter of `NEW` command the recipient gets automatically subscribed to receive the messages from that queue, until the transport connection is closed. To start receiving the messages from the existing queue when the new transport connection is opened the client must use `subscribe` command.
@@ -541,12 +646,24 @@ When the simplex queue was not created in the current transport connection, the
subscribe = %s"SUB"
```
If subscription is successful the server must respond with the first available message or with `ok` response if no messages are available. The recipient will continue receiving the messages from this queue until the transport connection is closed or until another transport connection subscribes to the same simplex queue - in this case the first subscription should be cancelled and [subscription END notification](#subscription-end-notification) delivered.
If subscription is successful the server must respond with the first available message or with [queue subscription response](#queue-subscription-response) (`SOK`) if no messages are available. The recipient will continue receiving the messages from this queue until the transport connection is closed or until another transport connection subscribes to the same simplex queue - in this case the first subscription should be cancelled and [subscription END notification](#subscription-end-notification) delivered.
The first message will be delivered either immediately or as soon as it is available; to receive the following message the recipient must acknowledge the reception of the message (see [Acknowledge message delivery](#acknowledge-message-delivery)).
This transmission and its response MUST be signed.
#### Subscribe to multiple queues
This command is used by recipient services to subscribe to multiple queues at once:
```abnf
subscribeMultiple = %s"SUBS " count idsHash
count = 8*8 OCTET ; Int64, network byte order (big-endian)
idsHash = 16*16 OCTET ; XOR of MD5 hashes of all queue IDs
```
The count and idsHash allow the server to detect subscription drift. The server responds with `serviceOkMultiple` (`SOKS`) response.
#### Secure queue by recipient
This command is only used until v8 of SMP protocol. V9 uses [SKEY](#secure-queue-by-sender).
@@ -565,6 +682,44 @@ Once the queue is secured only authorized messages can be sent to it.
This command MUST be used in transmission with recipient queue ID.
#### Set queue recipient keys
This command is used to set additional recipient keys to support shared management of the queue:
```abnf
recipientKeys = %s"RKEY " recipientKeysList
recipientKeysList = count 1*recipientKey ; non-empty list
count = 1*1 OCTET ; number of keys (1-255)
recipientKey = length x509encoded
```
This command added to allow multiple group owners manage data of the same queue link.
#### Set short link
This command is used to associate a short link with the queue:
```abnf
setShortLink = %s"LSET " linkId encFixedData encUserData
linkId = shortString
encFixedData = largeString ; encrypted fixed link data
encUserData = largeString ; encrypted user data (e.g., profile)
largeString = length2 *OCTET
length2 = 2*2 OCTET ; Word16, network byte order (big-endian)
```
The server responds with `OK` response if successful.
#### Delete short link
This command is used to remove a short link association from the queue:
```abnf
deleteShortLink = %s"LDEL"
```
The server responds with `OK` or `ERR`
#### Enable notifications command
This command is sent by the recipient to the server to add notifier's key to the queue, to allow push notifications server to receive notifications when the message arrives, via a separate queue ID, without receiving message content.
@@ -580,10 +735,10 @@ recipientNotificationDhPublicKey = length x509encoded
; using [NaCl crypto_box][16] encryption scheme (curve25519xsalsa20poly1305).
```
The server will respond with `notifierId` response if notifications were enabled and the notifier's key was successfully added to the queue:
The server will respond with `NID` response if notifications were enabled and the notifier's key was successfully added to the queue:
```abnf
notifierId = %s"NID " notifierId srvNotificationDhPublicKey
notifierIdResponse = %s"NID " notifierId srvNotificationDhPublicKey
notifierId = shortString ; 16-24 bytes
srvNotificationDhPublicKey = length x509encoded
; the server's Curve25519 key for DH exchange to derive the secret
@@ -1001,6 +1156,35 @@ The shared secret for encrypting transmission bodies between proxy server and de
relayResponse = %s"RRES" SP <encrypted(responseTransmission)>
```
### Short link commands
These commands are used by senders to access queues via short links (added in v8).
#### Set link key
This command is used to set the sender key and to get link data associated with a "messaging" queue:
```abnf
setLinkKey = %s"LKEY " senderAuthPublicKey
senderAuthPublicKey = length x509encoded
```
The server secures the queue with the provided key and responds with `LNK` response containing the sender ID and encrypted link data.
Once this command is used, the queue is secured, and the command can only be repeated with the same key.
#### Get link data
This command is used to retrieve the link data associated with a "contact" queue:
```abnf
getLinkData = %s"LGET"
```
The server responds with `LNK` response containing the sender ID and encrypted link data.
This command may be repeated multiple times.
### Notifier commands
#### Subscribe to queue notifications
@@ -1011,16 +1195,69 @@ The push notifications server (notifier) must use this command to start receivin
subscribeNotifications = %s"NSUB"
```
If subscription is successful the server must respond with `ok` response if no messages are available. The notifier will be receiving the message notifications from this queue until the transport connection is closed or until another transport connection subscribes to notifications from the same simplex queue - in this case the first subscription should be cancelled and [subscription END notification](#subscription-end-notification) delivered.
If subscription is successful the server must respond with [queue subscription response](#queue-subscription-response) (`SOK`). The notifier will be receiving the message notifications from this queue until the transport connection is closed or until another transport connection subscribes to notifications from the same simplex queue - in this case the first subscription should be cancelled and [subscription END notification](#subscription-end-notification) delivered.
The first message notification will be delivered either immediately or as soon as the message is available.
#### Subscribe to multiple queue notifications
This command is used by notifier services to subscribe to multiple queues at once:
```abnf
subscribeNotificationsMultiple = %s"NSUBS " count idsHash
count = 8*8 OCTET ; Int64, network byte order (big-endian)
idsHash = 16*16 OCTET ; XOR of MD5 hashes of all queue IDs
```
The server responds with `serviceOkMultiple` (`SOKS`) response.
### Server messages
This section includes server events and generic command responses used for several commands.
The syntax for command-specific responses is shown together with the commands.
#### Link response
Sent in response to `LKEY` and `LGET` commands:
```abnf
linkResponse = %s"LNK " senderId encFixedData encUserData
senderId = shortString ; the sender ID for the queue
encFixedData = largeString ; encrypted fixed link data
encUserData = largeString ; encrypted user data
```
#### Queue subscription response
Sent in response to `SUB` and `NSUB` commands:
```abnf
serviceOk = %s"SOK " optServiceId
optServiceId = %s"0" / (%s"1" serviceId)
serviceId = shortString
```
If response contains `serviceId`, it means that queue is associated with the service.
#### Service subscription response
Sent in response to `SUBS` or `NSUBS` commands:
```abnf
serviceOkMultiple = %s"SOKS " count idsHash
count = 8*8 OCTET ; Int64, network byte order (big-endian)
idsHash = 16*16 OCTET ; XOR of MD5 hashes of all subscribed queue IDs
```
#### All service messages received
Sent to indicate all messages have been delivered from all queues associated with the service:
```abnf
allReceived = %s"ALLS"
```
#### Deliver queue message
When server delivers the messages to the recipient, message body should be encrypted with the secret derived from DH exchange using the keys passed during the queue creation and returned with `queueIds` response.
@@ -1077,6 +1314,24 @@ unsubscribed = %s"END"
No further messages should be delivered to unsubscribed transport connection.
#### Service subscription END notification
Sent when service subscription is terminated (can be sent when service re-connects):
```abnf
serviceUnsubscribed = %s"ENDS " count idsHash
count = 8*8 OCTET ; Int64, network byte order (big-endian)
idsHash = 16*16 OCTET ; XOR of MD5 hashes of terminated queue IDs
```
#### Queue deleted notification
Sent when a queue has been deleted via another connection:
```abnf
deleted = %s"DELD"
```
#### Error responses
- incorrect block format, encoding or authorization size (`BLOCK`).
@@ -1100,6 +1355,7 @@ No further messages should be delivered to unsubscribed transport connection.
- `NETWORK` - network error.
- `TIMEOUT` - command response timeout.
- `HOST` - no compatible server host (e.g. onion when public is required, or vice versa)
- `NO_SERVICE` - service unavailable client-side.
- `TRANSPORT` - handshake or other transport error:
- `BLOCK` - error parsing transport block.
- `VERSION` - incompatible client or server version.
@@ -1111,25 +1367,42 @@ No further messages should be delivered to unsubscribed transport connection.
- `IDENTITY` - incorrect server identity (certificate fingerprint does not match server address).
- `BAD_AUTH` - incorrect or missing server credentials in handshake.
- authentication error (`AUTH`) - incorrect authorization, unknown (or suspended) queue, sender's ID is used in place of recipient's and vice versa, and some other cases (see [Send message](#send-message) command).
- blocked entity error (`BLOCKED`) - the entity (queue or message) was blocked due to policy violation (added in v17). Contains blocking information:
- `reason` - blocking reason (`spam` or `content`).
- `notice` - optional client notice with additional information.
- service error (`SERVICE`) - service-related error.
- crypto error (`CRYPTO`) - cryptographic operation failed.
- message queue quota exceeded error (`QUOTA`) - too many messages were sent to the message queue. Further messages can only be sent after the recipient retrieves the messages.
- store error (`STORE`) - server storage error with error message.
- message expired (`EXPIRED`) - message has expired.
- no message (`NO_MSG`) - no message available or message ID mismatch.
- sent message is too large (> 16064) to be delivered (`LARGE_MSG`).
- internal server error (`INTERNAL`).
- duplicate error (`DUPLICATE_`) - internal duplicate detection error (not returned by server).
The syntax for error responses:
```abnf
error = %s"ERR " errorType
errorType = %s"BLOCK" / %s"SESSION" / %s"CMD" SP cmdError / %s"PROXY" proxyError /
%s"AUTH" / %s"QUOTA" / %s"LARGE_MSG" / %s"INTERNAL"
cmdError = %s"SYNTAX" / %s"PROHIBITED" / %s"NO_AUTH" / %s"HAS_AUTH" / %s"NO_ENTITY"
errorType = %s"BLOCK" / %s"SESSION" / %s"CMD" SP cmdError / %s"PROXY" SP proxyError /
%s"AUTH" / %s"BLOCKED" SP blockingInfo / %s"SERVICE" / %s"CRYPTO" /
%s"QUOTA" / %s"STORE" SP storeError / %s"EXPIRED" / %s"NO_MSG" /
%s"LARGE_MSG" / %s"INTERNAL" / %s"DUPLICATE_"
cmdError = %s"UNKNOWN" / %s"SYNTAX" / %s"PROHIBITED" / %s"NO_AUTH" / %s"HAS_AUTH" / %s"NO_ENTITY"
proxyError = %s"PROTOCOL" SP errorType / %s"BROKER" SP brokerError /
%s"BASIC_AUTH" / %s"NO_SESSION"
brokerError = %s"RESPONSE" SP shortString / %s"UNEXPECTED" SP shortString /
%s"NETWORK" / %s"TIMEOUT" / %s"HOST" /
%s"NETWORK" [SP networkError] / %s"TIMEOUT" / %s"HOST" / %s"NO_SERVICE" /
%s"TRANSPORT" SP transportError
networkError = %s"CONNECT" SP shortString / %s"TLS" SP shortString /
%s"UNKNOWNCA" / %s"FAILED" / %s"TIMEOUT" / %s"SUBSCRIBE" SP shortString
transportError = %s"BLOCK" / %s"VERSION" / %s"LARGE_MSG" / %s"SESSION" / %s"NO_AUTH" /
%s"HANDSHAKE" SP handshakeError
handshakeError = %s"PARSE" / %s"IDENTITY" / %s"BAD_AUTH"
handshakeError = %s"PARSE" / %s"IDENTITY" / %s"BAD_AUTH" / %s"BAD_SERVICE"
blockingInfo = %s"reason=" blockingReason ["," %s"notice=" jsonNotice]
blockingReason = %s"spam" / %s"content"
jsonNotice = <JSON-encoded client notice>
storeError = *OCTET
```
Server implementations must aim to respond within the same time for each command in all cases when `"ERR AUTH"` response is required to prevent timing attacks (e.g., the server should verify authorization even when the queue does not exist on the server or the authorization of different type is sent, using any dummy key compatible with the used authorization).
@@ -1218,7 +1491,7 @@ The first block sent by the server should be `paddedServerHello` and the client
```abnf
paddedServerHello = <padded(serverHello, 16384)>
serverHello = smpVersionRange sessionIdentifier [serverCert signedServerKey] ignoredPart
serverHello = smpVersionRange sessionIdentifier [serverCertKey] ignoredPart
smpVersionRange = minSmpVersion maxSmpVersion
minSmpVersion = smpVersion
maxSmpVersion = smpVersion
@@ -1226,25 +1499,39 @@ sessionIdentifier = shortString
; unique session identifier derived from transport connection handshake
; it should be included in authorized part of all SMP transmissions sent in this transport connection,
; but it must not be sent as part of the transmission in the current protocol version.
serverCert = originalLength x509encoded
signedServerKey = originalLength x509encoded ; signed by server certificate
serverCertKey = certChain signedServerKey
certChain = count 1*cert ; 2-4 certificates
cert = originalLength x509encoded
signedServerKey = originalLength x509encoded ; X25519 key signed by server certificate
paddedClientHello = <padded(clientHello, 16384)>
clientHello = smpVersion [clientKey] ignoredPart
clientHello = smpVersion keyHash [clientKey] proxyServer optClientService ignoredPart
; chosen SMP protocol version - it must be the maximum supported version
; within the range offered by the server
clientKey = length x509encoded
keyHash = shortString ; server identity - CA certificate fingerprint
clientKey = length x509encoded ; X25519 public key for session encryption - only present if needed
proxyServer = %s"T" / %s"F" ; true if connecting client is a proxy server
optClientService = %s"0" / (%s"1" clientService) ; optional service client credentials
clientService = serviceRole serviceCertKey
serviceRole = %s"M" / %s"N" ; Messaging / Notifier
serviceCertKey = certChain signedServiceKey
signedServiceKey = originalLength x509encoded ; Ed25519 key signed by service certificate
smpVersion = 2*2OCTET ; Word16 version number
originalLength = 2*2OCTET
count = 1*1OCTET
ignoredPart = *OCTET
pad = *OCTET
```
`signedServerKey` is used to compute a shared secret to authorize client transmission - it is combined with the per-queue key that was used when the queue was created.
`signedServerKey` is used to compute a shared secret to authorize client transmissions - it is combined with the per-queue key that was used when the queue was created.
`clientKey` is used only by SMP proxy server when it connects to the destination server to agree shared secret for the additional encryption layer, end user clients do not use this key.
`proxyServer` flag (v14+) disables additional transport encryption inside TLS for proxy connections, since proxy server connection already has additional encryption.
`clientService` (v16+) provides long-term service client certificate for high-volume services using SMP server (chat relays, notification servers, high traffic bots). The server responds with a third handshake message containing the assigned service ID.
`ignoredPart` in handshake allows to add additional parameters in handshake without changing protocol version - the client and servers must ignore any extra bytes within the original block length.
For TLS transport client should assert that `sessionIdentifier` is equal to `tls-unique` channel binding defined in [RFC 5929][14] (TLS Finished message struct); we pass it in `serverHello` block to allow communication over some other transport protocol (possibly, with another channel binding).

View File

@@ -1,4 +1,4 @@
Version 2, 2024-06-22
Version 3, 2025-01-24
# SimpleX File Transfer Protocol
@@ -33,6 +33,7 @@ Version 2, 2024-06-22
- [File recipient commands](#file-recipient-commands)
- [Download file chunk](#download-file-chunk)
- [Acknowledge file chunk download](#acknowledge-file-chunk-download)
- [Error responses](#error-responses)
- [Threat model](#threat-model)
## Abstract
@@ -49,6 +50,12 @@ The objective of SimpleX File Transfer Protocol (XFTP) is to facilitate the secu
XFTP is implemented as an application level protocol on top of HTTP2 and TLS.
This document describes XFTP protocol version 3. The version history:
- v1: initial version
- v2: authenticated commands - added basic auth support for commands
- v3: blocked files - added BLOCKED error type for policy violations
The protocol describes the set of commands that senders and recipients can send to XFTP servers to create, upload, download and delete file chunks of several pre-defined sizes. XFTP servers SHOULD support chunks of 4 sizes: 64KB, 256KB, 1MB and 4MB (1KB = 1024 bytes, 1MB = 1024KB).
The protocol is designed with the focus on meta-data privacy and security. While using TLS, the protocol does not rely on TLS security by using additional encryption to achieve that there are no identifiers or ciphertext in common in received and sent server traffic, frustrating traffic correlation even if TLS is compromised.
@@ -283,7 +290,7 @@ XFTP server implementations MUST NOT create, store or send to any other servers:
- binary-encoded commands sent as fixed-size padded block in the body of HTTP2 POST request, similar to SMP and notifications server protocol transmission encodings.
- HTTP2 POST with a fixed size padded block body for file upload and download.
Block size - 4096 bytes (it would fit ~120 Ed25519 recipient keys).
Block size - 16384 bytes (it would fit ~350 Ed25519 recipient keys).
The reasons to use HTTP2:
@@ -320,12 +327,13 @@ Once TLS handshake is complete, client and server will exchange blocks of fixed
```abnf
paddedServerHello = <padded(serverHello, 16384)>
serverHello = xftpVersionRange sessionIdentifier serverCert signedServerKey ignoredPart
serverHello = xftpVersionRange sessionIdentifier serverCerts signedServerKey ignoredPart
xftpVersionRange = minXftpVersion maxXftpVersion
minXftpVersion = xftpVersion
maxXftpVersion = xftpVersion
sessionIdentifier = shortString
; unique session identifier derived from transport connection handshake
serverCerts = length 1*serverCert ; NonEmpty list of certificates in chain
serverCert = originalLength <x509encoded>
signedServerKey = originalLength <x509encoded> ; signed by server certificate
@@ -382,7 +390,7 @@ Commands syntax below is provided using ABNF with case-sensitive strings extensi
xftpCommand = ping / senderCommand / recipientCmd / serverMsg
senderCommand = register / add / put / delete
recipientCmd = get / ack
serverMsg = pong / sndIds / rcvIds / ok / file
serverMsg = pong / sndIds / rcvIds / ok / file / error
```
The syntax of specific commands and responses is defined below.
@@ -427,7 +435,7 @@ The syntax is:
register = %s"FNEW " fileInfo rcvPublicAuthKeys basicAuth
fileInfo = sndKey size digest
sndKey = length x509encoded
size = 1*DIGIT
size = 4*4 OCTET ; Word32 big-endian
digest = length *OCTET
rcvPublicAuthKeys = length 1*rcvPublicAuthKey
rcvPublicAuthKey = length x509encoded
@@ -509,7 +517,7 @@ If requested file is successfully located, the server must send `file` response.
```abnf
file = %s"FILE " sDhKey cbNonce
sDhKey = length x509encoded
cbNonce = <nonce used in NaCl crypto_box encryption scheme>
cbNonce = 24*24 OCTET ; NaCl crypto_box nonce
```
Chunk is additionally encrypted on the way from the server to the recipient using a key agreed via ephemeral DH keys `rDhKey` and `sDhKey`, so there is no ciphertext in common between sent and received traffic inside TLS connection, in order to complicate traffic correlation attacks, if TLS is compromised.
@@ -526,6 +534,40 @@ If file recipient ID is successfully deleted, the server must send `ok` response
In current implementation of XFTP protocol in SimpleX Chat clients don't use FACK command. Files are automatically expired on servers after configured time interval.
### Error responses
The server responds with `ERR` followed by the error type:
```abnf
error = %s"ERR " errorType
errorType = %s"BLOCK" / %s"SESSION" / %s"HANDSHAKE" /
%s"CMD" SP cmdError / %s"AUTH" / %s"BLOCKED" SP blockingInfo /
%s"SIZE" / %s"QUOTA" / %s"DIGEST" / %s"CRYPTO" /
%s"NO_FILE" / %s"HAS_FILE" / %s"FILE_IO" /
%s"TIMEOUT" / %s"INTERNAL"
cmdError = %s"UNKNOWN" / %s"SYNTAX" / %s"PROHIBITED" / %s"NO_AUTH" / %s"HAS_AUTH" / %s"NO_ENTITY"
blockingInfo = %s"reason=" blockingReason ["," %s"notice=" jsonNotice]
blockingReason = %s"spam" / %s"content"
jsonNotice = *OCTET ; JSON-encoded notice object
```
Error types:
- `BLOCK` - incorrect block format, encoding or signature size.
- `SESSION` - incorrect session ID (TLS Finished message / tls-unique binding).
- `HANDSHAKE` - incorrect handshake command.
- `CMD` - command syntax errors (UNKNOWN, SYNTAX, PROHIBITED, NO_AUTH, HAS_AUTH, NO_ENTITY).
- `AUTH` - command authorization error - bad signature or non-existing file chunk.
- `BLOCKED` - file chunk was blocked due to policy violation (added in v3). Contains blocking reason and optional notice.
- `SIZE` - incorrect file size.
- `QUOTA` - storage quota exceeded.
- `DIGEST` - incorrect file digest.
- `CRYPTO` - file encryption/decryption failed.
- `NO_FILE` - no expected file body in request/response or no file on the server.
- `HAS_FILE` - unexpected file body.
- `FILE_IO` - file IO error.
- `TIMEOUT` - file sending or receiving timeout.
- `INTERNAL` - internal server error.
## Threat model
#### Global Assumptions