|
| 1 | +# Protocol changes for creating and connecting to SMP queues |
| 2 | + |
| 3 | +## Problems |
| 4 | + |
| 5 | +This change is related to these problems: |
| 6 | +- differentiating queue retention time, |
| 7 | +- supporting MITM-resistant short connection links, |
| 8 | +- improving notifications. |
| 9 | + |
| 10 | +This RFC is based on the previous discussions about short links, blob storage and notifications ([1](./2024-06-21-short-links.md), [2](./2024-09-09-smp-blobs.md), [3](./2024-11-25-queue-blobs-2.md), [4](./2024-09-25-ios-notifications-2.md)). |
| 11 | + |
| 12 | +SMP protocol supports two types of queues - queues to communicate over and queues to send invitations. While SMP protocol was originally "unaware" of these queue types, it could differentiate it by message flow, and with the recent addition of SKEY command to allow securing the queue by the sender this difference became persistent. |
| 13 | + |
| 14 | +Simply designating queue types would allow to use this information to decide for how long to retain queues, and potentially extending it: |
| 15 | +- unsecured 1-time invitation queues with sndSecure (support of securing by sender) - e.g., 3 months. |
| 16 | +- contact address queues without sndSecure - e.g., 3 years without activity. |
| 17 | +- Possibly, "queues" that prohibit messages and used only as blob storage - they would be used to store group profiles and superpeer addresses for the group. |
| 18 | + |
| 19 | +This proposal also combines NEW and NKEY command to streamline notifications in preparation to reworking of the notifications protocol. |
| 20 | + |
| 21 | +## Design objectives |
| 22 | + |
| 23 | +We want to achieve these objectives: |
| 24 | +1. no possibility to provide incorrect SenderId inside link data (e.g. from another queue). |
| 25 | +2. link data cannot be accessed by the server unless it has the link. |
| 26 | +3. prevent MITM attack by the server, including the server that obtained the link. |
| 27 | +4. prevent changing of connection request by the user (to prevent MITM via break-in attack in the originating client). |
| 28 | +5. for one-time links, prevent accessing link data by link observers who did not compromise the server. |
| 29 | +6. allow changing the user-defined part of link data. |
| 30 | +7. avoid changing the link when user-defined part of link data changes, while preventing MITM attack by the server on user-defined part, even if it has the link. |
| 31 | +8. retain the quality that it is impossible to check the existense of secured queue from having any of its temporary visible IDs (sender ID and link ID in 1-time invitations) - it requires that these IDs remain server-generated (contrary to the previous RFCs). |
| 32 | + |
| 33 | +To achieve these objectives the queue data must have immutable part and mutable part. |
| 34 | + |
| 35 | +Immutable part would include: |
| 36 | +- full conection request (the current long link with all keys, including PQ keys). This includes SenderId that must match server response. |
| 37 | +- public signature key to verify mutable part of link data. |
| 38 | + |
| 39 | +Signed mutable part would inlcude: |
| 40 | +- any links to chat relays that should be contacted instead of this queue (not in this RFC), but would allow delegating group connections and contact request connections to prevent spam, hiding online presense, etc. |
| 41 | +- and user-defined data - user profile or group profile. |
| 42 | + |
| 43 | +The link itself should include both the key and auth tag from the encryption of immutable part. Accessing one-time link data should require providing sender key and signing the command (`LKEY`). |
| 44 | + |
| 45 | +## Solution |
| 46 | + |
| 47 | +Current NEW and NKEY commands: |
| 48 | + |
| 49 | +```haskell |
| 50 | +NEW :: RcvPublicAuthKey -> RcvPublicDhKey -> Maybe BasicAuth -> SubscriptionMode -> SenderCanSecure -> Command Recipient |
| 51 | + |
| 52 | +NKEY :: NtfPublicAuthKey -> RcvNtfPublicDhKey -> Command Recipient |
| 53 | + |
| 54 | +-- | Queue IDs and keys, returned in IDS response |
| 55 | +data QueueIdsKeys = QIK |
| 56 | + { rcvId :: RecipientId, |
| 57 | + sndId :: SenderId, |
| 58 | + rcvPublicDhKey :: RcvPublicDhKey, |
| 59 | + sndSecure :: SenderCanSecure |
| 60 | + } |
| 61 | +``` |
| 62 | + |
| 63 | +Proposed NEW command replaces SenderCanSecure with QueueMode, adds link data, and combines NKEY command: |
| 64 | + |
| 65 | +```haskell |
| 66 | +NEW :: NewQueueRequest -> Command Recipient |
| 67 | + |
| 68 | +data NewQueueRequest = NewQueueRequest |
| 69 | + { rcvAuthKey :: RcvPublicAuthKey, |
| 70 | + rcvDhKey :: RcvPublicDhKey, |
| 71 | + basicAuth :: Maybe BasicAuth, |
| 72 | + subMode :: SubscriptionMode, |
| 73 | + ntfRequest :: Maybe NtfRequest, |
| 74 | + queueLink :: Maybe QueueLink -- it is Maybe to allow testing and staged roll-out |
| 75 | + } |
| 76 | + |
| 77 | +-- To allow updating the existing contact addresses without changing them. |
| 78 | +-- This command would fail on queues that support sndSecure and also on new queues created with QLMessaging. |
| 79 | +-- RecipientId is entity ID. |
| 80 | +-- The response to this command is `OK`. |
| 81 | +LNEW :: LinkId -> QueueLinkData -> Command Recipient |
| 82 | + |
| 83 | +-- Replaces NKEY command |
| 84 | +-- This avoids additional command required from the client to enable notifications. |
| 85 | +-- Further changes would move NotifierId generation to the client, and including a signed and encrypted command to be forwarded by SMP server to notification server. |
| 86 | +data NtfRequest = NtfRequest NtfPublicAuthKey RcvNtfPublicDhKey |
| 87 | + |
| 88 | +-- QLMessaging implies that sender can secure the queue. |
| 89 | +-- LinkId is not used with QLMessaging, to prevent the possibility of checking when connection is established by re-using the same link ID when creating another queue – the creating would have to fail if it is used. |
| 90 | +-- LinkId is required with QLContact, to have shorter link - it will be derived from the link_uri. And in this case we do not need to prevent checks that this queue exists. |
| 91 | +data QueueLink = QLMessaging QueueLinkData | QLContact LinkId QueueLinkData |
| 92 | + |
| 93 | +data QueueLinkData = QueueLinkData EncImmutableDataBytes EncUserDataBytes |
| 94 | + |
| 95 | +newtype EncImmutableDataBytes = EncImmutableDataBytes ByteString |
| 96 | + |
| 97 | +newtype EncUserDataBytes = EncUserDataBytes ByteString |
| 98 | + |
| 99 | +-- We need to use binary encoding for AConnectionRequestUri to reduce its size |
| 100 | +-- connReq including the full link allows connection redundancy. |
| 101 | +-- The clients would reject changed immutable data (based on auth tag in the link) and |
| 102 | +-- AConnectionRequestUri where SenderId of the queue does not match. |
| 103 | +data ImmutableLinkData = ImmutableLinkData |
| 104 | + { signature :: SignatureEd25519, -- signature of the remaining part of immutable data |
| 105 | + connReq :: AConnectionRequestUri, |
| 106 | + sigKey :: PublicKeyEd25519 |
| 107 | + } |
| 108 | + |
| 109 | +-- This part of link data can also include any relays, but possibly we need a separate blob for it |
| 110 | +data UserLinkData = UserLinkData |
| 111 | + { signature :: SignatureEd25519, -- signs the remaining part of the data |
| 112 | + userData :: ByteString -- the max size needs to be estimated, but it is likely to be ~ 14kb |
| 113 | + } |
| 114 | + |
| 115 | +-- | Updated queue IDs and keys, returned in IDS response |
| 116 | +data QueueIdsKeys = QIK |
| 117 | + { rcvId :: RecipientId, -- server-generated |
| 118 | + sndId :: SenderId, -- server-generated |
| 119 | + rcvPublicDhKey :: RcvPublicDhKey, |
| 120 | + sndSecure :: SenderCanSecure, -- possibly, can be removed? or implied? |
| 121 | + linkId :: Maybe LinkId, -- server-generated |
| 122 | + serverNtfCreds :: Maybe ServerNtfCreds -- currently returned in NID response |
| 123 | + } |
| 124 | + |
| 125 | +data ServerNtfCreds = ServerNtfCreds NotifierId RcvNtfPublicDhKey -- NotifierId is server-generated. |
| 126 | +``` |
| 127 | + |
| 128 | +In addition to that we add the command allowing to update and also to retrieve and, optionally, secure the queue and get link data in one request, to have only one request: |
| 129 | + |
| 130 | +```haskell |
| 131 | +-- With RecipientId as entity ID, the command to update mutable part of link data |
| 132 | +-- The response is OK here. |
| 133 | +LSET :: EncUserDataBytes -> Command Recipient |
| 134 | + |
| 135 | +-- To be used with 1-time links. |
| 136 | +-- Sender's key provided on the first request prevents observers from undetectably accessing 1-time link data. |
| 137 | +-- If queue mode is QLContact (and queue does NOT allow sndSecure) the command will fail, same as SKEY. |
| 138 | +-- Once queue is secured, the key must be the same in subsequent requests - to allow retries in case of network failures, and to prevent passive attacks. |
| 139 | +-- The difference with securing queues is that queues allow sending unsecured messages to queues that allow sndSecure (for backwards compatibility), and 1-time links will NOT allow retrieving link data without securing the queue at the same time, preventing undetected access by observers. |
| 140 | +-- Entity ID is LinkId here |
| 141 | +LKEY :: SndPublicAuthKey -> Command Sender |
| 142 | + |
| 143 | +-- If queue mode is QLMessaging the command will fail. |
| 144 | +-- Entity ID is LinkId here |
| 145 | +LGET :: Command Sender |
| 146 | + |
| 147 | +-- Response to LKEY and LGET |
| 148 | +-- Entity ID is LinkId here |
| 149 | +LINK :: SenderId -> QueueLinkData -> BrokerMsg |
| 150 | +``` |
| 151 | + |
| 152 | +## Algorithm to prepare and to interpret queue link data. |
| 153 | + |
| 154 | +For contact addresses this approach follows the design proposed in [Short links](./2024-06-21-short-links.md) RFC - when link id is derived from the same random binary as key. For 1-time invitations link ID is independent and server-generated, to prevent existense checks. |
| 155 | + |
| 156 | +**Prepare queue link data** |
| 157 | + |
| 158 | +- the queue owner generates a random 256 bit `link_key` that will be used in the link URI. |
| 159 | +- for 1-time links: crypto_box key and 2 nonces to encrypt link data are derived from link_uri using HKDF: `cb_key <> nonce1 <> nonce2 = HKDF(link_key, 80 bytes)` (nonce1 is used for immutable and nonce2 for user-defined parts). |
| 160 | +- for contact address links: key and 2 nonces and linkId will be derived: `link_id <> cb_key <> nonce1 <> nonce2 = HKDF(link_key, 104 bytes)` |
| 161 | +- both parts of link data are encrypted with crypto_box, and included into `NEW` or `LNEW` commands. |
| 162 | + |
| 163 | +**Retrieving queue link data** |
| 164 | + |
| 165 | +- the sender uses `LinkId` from URI (or derived from URI) as entity ID to retrieve link data. |
| 166 | +- for one time links the sender must authorize the request to retrieve the data, the key is provided with the first request, preventing undetected access by link observers. |
| 167 | +- having received the link data, the client can now decrypt it using secret_box. |
| 168 | + |
| 169 | +## Threat model |
| 170 | + |
| 171 | +**Compromised SMP server** |
| 172 | + |
| 173 | +can: |
| 174 | +- delete link data. |
| 175 | +- hide link selectively from some requests. |
| 176 | + |
| 177 | +cannot: |
| 178 | +- undetectably replace link data, even if they have the link (objective 3). |
| 179 | +- access unencrypted link data, whether it was or was not accessed by the accepting party, provided it has no link (objective 2). |
| 180 | +- observe IP addresses of the users accessing link data, if private routing is used. |
| 181 | + |
| 182 | +**Passive observer who observed short link**: |
| 183 | + |
| 184 | +can: |
| 185 | +- access original unencrypted link data for contact address links. |
| 186 | + |
| 187 | +cannot: |
| 188 | +- undetectably access observed 1-time link data, accessing the link would make the link inaccessible to the sender (objective 5). |
| 189 | +- undetectbly check the existense of messaging queue or 1-time link (objective 8). |
| 190 | +- replace or delete the link data. |
| 191 | + |
| 192 | +**Queue owner who did not comprmise the server**: |
| 193 | + |
| 194 | +cannot: |
| 195 | +- redirect connecting user to another queue, on the same or on another server (objective 1). |
| 196 | +- replace connection request in the link (objective 4). |
| 197 | + |
| 198 | +## Correlation of design objectives with design elements |
| 199 | + |
| 200 | +1. The presense of `SenderId` in `LINK` response from the server. |
| 201 | +2. Encryption of link data with crypto_box. |
| 202 | +3. Auth tag in the link prevents server modification of immutable part of link data. Signature verification key in immutable part, and signing of mutable part prevents server modification of mutable part of link data. |
| 203 | +4. No server command to change immutable part of link data once it's set. |
| 204 | +5. 1-time link data can only be accessed with `LKEY` command, that while allows retries to mitigate network failures, will require the same key for retries. |
| 205 | +6. `LSET` command. |
| 206 | +7. The link only includes auth tag for immutable part, mutable part includes signature. |
| 207 | +8. Temporarily public IDs (SenderId and LinkId for 1-time invitations) are generated server-side, and cannot be provided by the clients when creating the queues to check if these IDs are free. |
| 208 | + |
| 209 | +## Syntax for short links |
| 210 | + |
| 211 | +The proposed syntax: |
| 212 | + |
| 213 | +```abnf |
| 214 | +shortConnectionLink = %s"https://" smpServerHost "/" linkUri [ "?" param *( "&" param ) ] |
| 215 | +smpServerHost = <hostname> ; RFC1123, RFC5891 |
| 216 | +linkUri = %s"i#" serverInfo oneTimeLinkBytes / %s"c#" serverInfo contactLinkBytes |
| 217 | +oneTimeLinkBytes = <base64url(linkId | linkKey | linkAuthTag)> ; 60 bytes / 80 base64 encoded characters |
| 218 | +contactLinkBytes = <base64url(linkKey | linkAuthTag)> ; 48 bytes / 64 base64 encoded characters |
| 219 | +; linkId - 96 bits/24 bytes |
| 220 | +; linkKey - 256 bits/32 bytes |
| 221 | +; linkAuthTag - 128 bits/16 bytes auth tag from encryption of immutable link data> |
| 222 | +
|
| 223 | +serverInfo = [fingerprint "@" [hostnames "/"]] ; not needed for preset servers, required otherwise - the clients must refuse to connect if they don't have fingerprint in the code. |
| 224 | +
|
| 225 | +fingerprint = <base64url(server offline certificate fingerprint)> |
| 226 | +hostnames = "h=" <hostname> *( "," <hostname> ) ; additional hostnames, e.g. onion |
| 227 | +``` |
| 228 | + |
| 229 | +To have shorter links fingerpring and additional server hostnames do not need to be specified for preconfigured servers, even if they are disabled - they can be used from the client code. Any user defined servers will require including additional hosts and server fingerprint. |
| 230 | + |
| 231 | +Example one-time link for preset server (108 characters): |
| 232 | + |
| 233 | +``` |
| 234 | +https://smp12.simplex.im/i#abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789 |
| 235 | +``` |
| 236 | + |
| 237 | +Example contact link for preset server (92 characters): |
| 238 | + |
| 239 | +``` |
| 240 | +https://smp12.simplex.im/c#abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcd |
| 241 | +``` |
| 242 | + |
| 243 | +Example contact link for user-defined server (with fingerprint, but without onion hostname - 136 characters): |
| 244 | + |
| 245 | +``` |
| 246 | +https://smp1.example.com/c#0YuTwO05YJWS8rkjn9eLJDjQhFKvIYd8d4xG8X1blIU@abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcd |
| 247 | +``` |
| 248 | + |
| 249 | +Example contact link for user-defined server (with fingerprint ant onion hostname - 199 characters): |
| 250 | + |
| 251 | +``` |
| 252 | +https://smp1.example.com/c#0YuTwO05YJWS8rkjn9eLJDjQhFKvIYd8d4xG8X1blIU@beccx4yfxxbvyhqypaavemqurytl6hozr47wfc7uuecacjqdvwpw2xid.onion/abcdefghij0123456789abcdefghij0123456789abcdefghij0123456789abcd |
| 253 | +``` |
| 254 | + |
| 255 | +For the links to work in the browser the servers must provide server pages. |
0 commit comments