Skip to content

Commit 73cad5a

Browse files
authored
simple per-queue e2e encryption with NaCl crypto_box (#242)
* simple per-queue e2e encryption with NaCl crypto_box * add e2e keys and DH secrets to schema * agree and save shared DH secret per queue (not used yet) * protocol changes for uniform padding and message part lengths * correct message structure diagrams * make per-queue E2E encryption non-optional * refactor crypto keys * use NaCl crypto_box for per-queue E2E encryption, remove RSA keys from queues * remove RSA support * merge migration with E2E DH keys * clean up * remove unused methods * parsing/serializing agent messages * remove sender timestamp from DB and code * clean up * slean up * s/SMPConfMsg/SMPConfirmation/ * serializeAgentMessage = serializeClientMessage . agentToClientMsg * simplify error handling * update protocol docs
1 parent 51a9750 commit 73cad5a

26 files changed

+1101
-1158
lines changed

apps/smp-server/Main.hs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ serverConfig =
3939
msgQueueQuota = 256,
4040
queueIdBytes = 24,
4141
msgIdBytes = 24, -- must be at least 24 bytes, it is used as 192-bit nonce for XSalsa20
42-
blockSize = 16 * 1024, -- TODO move to Protocol
4342
-- below parameters are set based on ini file /etc/opt/simplex/smp-server.ini
4443
transports = undefined,
4544
storeLog = undefined,

migrations/20210101_initial.sql

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ CREATE TABLE IF NOT EXISTS rcv_queues(
1111
rcv_id BLOB NOT NULL,
1212
conn_alias BLOB NOT NULL,
1313
rcv_private_key BLOB NOT NULL,
14+
e2e_priv_key BLOB NOT NULL,
15+
e2e_snd_pub_key BLOB,
16+
e2e_dh_secret BLOB,
1417
snd_id BLOB NOT NULL,
1518
snd_key BLOB,
16-
decrypt_key BLOB NOT NULL,
17-
verify_key BLOB,
1819
status TEXT NOT NULL,
1920
PRIMARY KEY (host, port, rcv_id),
2021
FOREIGN KEY (host, port) REFERENCES servers (host, port),
@@ -31,8 +32,8 @@ CREATE TABLE IF NOT EXISTS snd_queues(
3132
snd_id BLOB NOT NULL,
3233
conn_alias BLOB NOT NULL,
3334
snd_private_key BLOB NOT NULL,
34-
encrypt_key BLOB NOT NULL,
35-
sign_key BLOB NOT NULL,
35+
e2e_pub_key BLOB NOT NULL,
36+
e2e_dh_secret BLOB NOT NULL,
3637
status TEXT NOT NULL,
3738
PRIMARY KEY (host, port, snd_id),
3839
FOREIGN KEY (host, port) REFERENCES servers (host, port),
@@ -87,7 +88,6 @@ CREATE TABLE IF NOT EXISTS rcv_messages(
8788
internal_rcv_id INTEGER NOT NULL,
8889
internal_id INTEGER NOT NULL,
8990
external_snd_id INTEGER NOT NULL,
90-
external_snd_ts TEXT NOT NULL,
9191
broker_id BLOB NOT NULL,
9292
broker_ts TEXT NOT NULL,
9393
rcv_status TEXT NOT NULL,

migrations/20210624_confirmations.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
CREATE TABLE conn_confirmations (
22
confirmation_id BLOB NOT NULL PRIMARY KEY,
33
conn_alias BLOB NOT NULL REFERENCES connections ON DELETE CASCADE,
4+
e2e_snd_pub_key BLOB NOT NULL,
45
sender_key BLOB NOT NULL,
56
sender_conn_info BLOB NOT NULL,
67
accepted INTEGER NOT NULL,

protocol/agent-protocol.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -142,17 +142,15 @@ agentMessage = helloMsg / replyQueueMsg /
142142
143143
msgPadding = *OCTET ; optional random bytes to get messages to the same size (as defined in SMP message size)
144144
145-
helloMsg = %s"HELLO" SP signatureVerificationKey [SP %s"NO_ACK"]
146-
; NO_ACK means that acknowledgements to client messages will NOT be sent in this connection by the agent that sent `HELLO` message.
147-
signatureVerificationKey = encoded
145+
helloMsg = %s"HELLO"
148146
149147
replyQueueMsg = %s"REPLY" SP connectionRequest ; `connectionRequest` is defined below
150148
; this message can only be sent by the second connection party
151149
152-
clientMsg = %s"MSG" SP size CRLF clientMsgBody CRLF ; CRLF is in addition to CRLF in decryptedSmpMessageBody
153-
size = 1*DIGIT
150+
clientMsg = %s"MSG" SP clientMsgBody
154151
clientMsgBody = *OCTET
155152
153+
; TODO remove and move to "public" header
156154
invitationMsg = %s"INV" SP connReqInvitation SP connInfo
157155
; `connReqInvitation` and `connInfo` are defined below
158156
@@ -303,7 +301,7 @@ messageError = %s"MERR" SP agentMsgId SP <errorType>
303301
message = %s"MSG" SP msgIntegrity SP recipientMeta SP brokerMeta SP senderMeta SP binaryMsg
304302
recipientMeta = %s"R=" agentMsgId "," agentTimestamp ; receiving agent message metadata
305303
brokerMeta = %s"B=" brokerMsgId "," brokerTimestamp ; broker (server) message metadata
306-
senderMeta = %s"S=" agentMsgId "," agentTimestamp ; sending agent message metadata
304+
senderMeta = %s"S=" agentMsgId ; sending agent message ID
307305
brokerMsgId = encoded
308306
brokerTimestamp = <date-time>
309307
msgIntegrity = ok / msgIntegrityError

protocol/simplex-messaging.md

Lines changed: 142 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
- [Simplex queue IDs](#simplex-queue-ids)
1515
- [Server security requirements](#server-security-requirements)
1616
- [Message delivery notifications](#message-delivery-notifications)
17+
- [SMP Transmission structure](#smp-transmission-structure)
1718
- [SMP commands](#smp-commands)
1819
- [Correlating responses with commands](#correlating-responses-with-commands)
1920
- [Command authentication](#command-authentication)
@@ -116,13 +117,22 @@ The SMP queue URIs MUST include server identity, queue hostname, an optional por
116117
The [ABNF][8] syntax of the queue URI is:
117118

118119
```abnf
119-
queueURI = %s"smp://" smpServer "/" queueId ["#"]
120-
smpServer = serverIdentity "@" srvHost [":" port]
120+
queueURI = %s"smp://" smpServer "/" queueId "#" recipientDhPublicKey
121+
smpServer = serverIdentity "@" srvHost [":" port]
121122
srvHost = <hostname> ; RFC1123, RFC5891
122123
port = 1*DIGIT
123124
serverIdentity = base64url
124125
queueId = base64url
125126
base64url = <base64url encoded binary> ; RFC4648, section 5
127+
recipientDhPublicKey = dhPublicKey
128+
dhPublicKey = encryptionScheme ":" x509UrlEncoded
129+
; the recipient's key for DH exchange to derive the secret
130+
; that the sender will use to encrypt delivered messages
131+
132+
encryptionScheme = %s"x25519"
133+
; x25519 scheme means [NaCl crypto_box][16] encryption scheme (curve25519xsalsa20poly1305).
134+
135+
x509UrlEncoded = <base64url X509 key encoding>
126136
```
127137

128138
`hostname` can be IP address or domain name, as defined in RFC 1123, section 2.1.
@@ -345,24 +355,35 @@ The clients can optionally instruct a dedicated push notification server to subs
345355
- `subscribeNotifications` (`"NSUB"`) - see [Subscribe to queue notifications](#subscribe-to-queue-notifications).
346356
- `messageNotification` (`"NMSG"`) - see [Deliver message notification](#deliver-message-notification).
347357

348-
## SMP commands
358+
## SMP Transmission structure
349359

350-
Commands syntax below is provided using [ABNF][8] with [case-sensitive strings extension][8a].
360+
Each transport block (SMP transmission) has a fixed size of 16384 bytes for traffic uniformity.
351361

352-
Each transmission between the client and the server must have this format/syntax (after the decryption):
362+
Some parts of SMP transmission are padded to a fixed size; this padding is uniformly added as a word16 encoded in network byte order - see `paddedString` syntax.
353363

354-
```abnf
355-
transmission = [signature] SP signedSize SP signed SP pad ; pad to the fixed block size
356-
signedSize = 1*DIGIT
357-
signed = sessionIdentifier SP [corrId] SP [queueId] SP cmd ; corrId is required in client commands and server responses,
358-
; corrId is empty in server notifications.
359-
cmd = ping / recipientCmd / send / subscribeNotifications / serverMsg
360-
recipientCmd = create / subscribe / secure / enableNotifications /
361-
acknowledge / suspend / delete
362-
serverMsg = queueIds / message / notifierId / messageNotification /
363-
unsubscribed / ok / error
364-
corrId = 1*(%x21-7F) ; any characters other than control/whitespace
365-
queueId = encoded ; empty queue ID is used with "create" command
364+
In places where some part of the transmission should be padded, the syntax for `paddedNotation` is used:
365+
366+
```
367+
paddedString = originalLength string pad
368+
originalLength = 2*2 OCTET
369+
pad = N*N"#" ; where N = paddedLength - originalLength - 2
370+
371+
paddedNotation = <padded(string, paddedLength)>
372+
; string - un-padded string
373+
; paddedLength - required length after padding, including 2 bytes for originalLength
374+
```
375+
376+
Each transmission between the client and the server must have this format/syntax:
377+
378+
```
379+
paddedTransmission = <padded(transmission), 16384>
380+
transmission = [signature] SP signed
381+
signed = sessionIdentifier SP [corrId] SP [queueId] SP smpCommand
382+
; corrId is required in client commands and server responses,
383+
; it is empty in server notifications.
384+
corrId = 1*32(%x21-7F) ; any characters other than control/whitespace
385+
queueId = encoded ; max 32 bytes when decoded (24 bytes is used),
386+
; empty queue ID is used with "create" command and in some server responses
366387
signature = encoded
367388
; empty signature can be used with "send" before the queue is secured with secure command
368389
; signature is always empty with "ping" and "serverMsg"
@@ -371,6 +392,18 @@ encoded = <base64 encoded binary>
371392

372393
`base64` encoding should be used with padding, as defined in section 4 of [RFC 4648][9]
373394

395+
## SMP commands
396+
397+
Commands syntax below is provided using [ABNF][8] with [case-sensitive strings extension][8a].
398+
399+
```abnf
400+
smpCommand = ping / recipientCmd / send / subscribeNotifications / serverMsg
401+
recipientCmd = create / subscribe / secure / enableNotifications /
402+
acknowledge / suspend / delete
403+
serverMsg = queueIds / message / notifierId / messageNotification /
404+
unsubscribed / ok / error
405+
```
406+
374407
The syntax of specific commands and responses is defined below.
375408

376409
### Correlating responses with commands
@@ -534,10 +567,16 @@ Currently SMP defines only one command that can be used by senders - `send` mess
534567
This command is sent to the server by the sender both to confirm the queue after the sender received out-of-band message from the recipient and to send messages after the queue is secured:
535568

536569
```abnf
537-
send = %s"SEND" SP size SP msgBody SP
538-
; the last SP is in addition to SP in the transmission
539-
size = 1*DIGIT ; size in bytes
540-
msgBody = *OCTET ; any binary content of specified size
570+
send = %s"SEND" SP smpEncMessage
571+
smpEncMessage = smpPubHeader sentMsgBody ; message up to 15968 bytes
572+
smpPubHeader = smpClientVersion encodedLenKey
573+
smpClientVersion = word16
574+
encodedLenKey = keyLen x509binary
575+
keyLen = word16
576+
x509binary = <binary X509 key encoding>
577+
sentMsgBody = 15842*15842 OCTET
578+
; E2E-encrypted smpClientMessage padded to 15842 bytes before encryption
579+
word16 = 2*2 OCTET
541580
```
542581

543582
The first message is sent to confirm the queue - it should contain sender's server key (see decrypted message syntax below) - this first message must be sent without signature.
@@ -555,15 +594,85 @@ Until the queue is secured, the server should accept any number of unsigned mess
555594
The body should be encrypted with the recipient's "public" key (`EK`); once decrypted it must have this format:
556595

557596
```abnf
558-
decryptedBody = [clientHeader] CRLF clientBody CRLF
559-
clientHeader = senderKeyMsg
560-
senderKeyMsg = %s"KEY" SP senderKey
561-
senderKey = signatureScheme ":" x509encoded ; the sender's public key to sign SEND commands for this queue
562-
clientBody = *OCTET
597+
sentMsgBody = <encrypted padded(smpClientMessage, 15842)>
598+
smpClientMessage = smpPrivHeader clientMsgBody
599+
smpPrivHeader = emptyHeader / smpConfirmationHeader
600+
emptyHeader = " "
601+
smpConfirmationHeader = %s"K" senderKey
602+
senderKey = encodedLenKey ; the sender's public key to sign SEND commands for this queue
603+
clientMsgBody = *OCTET ; up to 15784 in case of emptyHeader
563604
```
564605

565606
`clientHeader` in the initial unsigned message is used to transmit sender's server key and can be used in the future revisions of SMP protocol for other purposes.
566607

608+
SMP transmission structure for sent messages:
609+
610+
```
611+
------- transmission (= 16384 bytes)
612+
2 | originalLength
613+
398- | signature SP sessionId SP corrId SP queueId SP %s"SEND" SP
614+
....... smpEncMessage (= 15968 bytes)
615+
126- | smpPubHeader
616+
24 | nonce for smpClientMessage
617+
------- smpClientMessage (E2E encrypted, = 15842 bytes)
618+
2 | originalLength
619+
16- | smpPrivHeader
620+
.......
621+
| clientMsgBody (<= 15784 bytes)
622+
.......
623+
0+ | smpClientMessage pad
624+
------- smpClientMessage end
625+
16 | auth tag for smpClientMessage
626+
....... smpEncMessage end
627+
16+ | transmission pad
628+
------- transmission end
629+
```
630+
631+
SMP transmission structure for received messages:
632+
633+
```
634+
------- transmission (= 16384 bytes)
635+
2 | originalLength
636+
398- | signature SP sessionId SP corrId SP queueId SP %s"MSG" SP msgId SP timestamp SP
637+
------- serverEncryptedMsg (= 15986 bytes)
638+
2 | originalLength
639+
....... smpEncMessage (= 15968 bytes)
640+
126- | smpPubHeader
641+
24 | nonce for smpClientMessage
642+
------- smpClientMessage (E2E encrypted, = 15842 bytes)
643+
2 | originalLength
644+
16- | smpPrivHeader
645+
....... clientMsgBody (<= 15784 bytes)
646+
-- TODO move internal structure to agent protocol
647+
16- | agentPublicHeader
648+
....... E2E double-ratchet encrypted (= 15768)
649+
96 | double-ratchet header
650+
16 | double-ratchet header auth tag
651+
24 | double-ratchet header iv
652+
------- encrypted agent message (= 15616 bytes)
653+
2 | originalLength
654+
122 (90) | agentHeader
655+
4 | %s"MSG" SP
656+
.......
657+
| application message (<= 15488 bytes)
658+
.......
659+
0+ | encrypted agent message pad
660+
------- encrypted agent message end
661+
16 | auth tag (IV generated from chain ratchet)
662+
....... E2E double-ratchet encrypted end
663+
|
664+
....... clientMsgBody end
665+
0+ | smpClientMessage pad
666+
------- smpClientMessage end
667+
16 | auth tag for smpClientMessage
668+
....... smpEncMessage end
669+
16 | auth tag (msgId is used as nonce)
670+
0+ | serverEncryptedMsg pad
671+
------- serverEncryptedMsg end
672+
0+ | transmission pad
673+
------- transmission end
674+
```
675+
567676
### Notifier commands
568677

569678
#### Subscribe to queue notifications
@@ -591,9 +700,9 @@ See its syntax in [Create queue command](#create-queue-command)
591700
The server must deliver messages to all subscribed simplex queues on the currently open transport connection. The syntax for the message delivery is:
592701

593702
```abnf
594-
message = %s"MSG" SP encryptedMessage
595-
encryptedMessage = <encrypt sentMessage>
596-
sentMessage = msgId SP timestamp SP size SP msgBody SP
703+
message = %s"MSG" SP msgId SP timestamp SP encryptedMsgBody
704+
encryptedMsgBody = <encrypt paddedSentMsgBody> ; server-encrypted padded sent msgBody
705+
paddedSentMsgBody = <padded(sentMsgBody, maxMessageLength + 2)> ; maxMessageLength = 15968
597706
msgId = encoded
598707
timestamp = <date-time defined in RFC3339>
599708
```
@@ -602,8 +711,6 @@ timestamp = <date-time defined in RFC3339>
602711

603712
`timestamp` - the UTC time when the server received the message from the sender, must be in date-time format defined by [RFC 3339][10]
604713

605-
`msgBody` - see syntax in [Send message](#send-message)
606-
607714
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.
608715

609716
This is done to prevent the possibility of correlation of incoming and outgoing traffic of SMP server inside transport protocol.
@@ -637,24 +744,24 @@ No further messages should be delivered to unsubscribed transport connection.
637744
#### Error responses
638745

639746
- incorrect block format, encoding or signature size (`BLOCK`).
747+
- missing or different session ID - tls-unique binding of TLS transport (`SESSION`)
640748
- command errors (`CMD`):
641749
- error parsing command (`SYNTAX`)
642750
- prohibited command (`PROHIBITED`) - any server response sent from client or `ACK` sent without active subscription or without message delivery.
643-
- incorrect RSA key size in `NEW` or `KEY` commands - only 1024, 2048 and 4096-bit keys are allowed (`KEY_SIZE`).
644751
- transmission has no required signature or queue ID (`NO_AUTH`)
645752
- transmission has unexpected credentials (`HAS_AUTH`)
646753
- transmission has no required queue ID (`NO_QUEUE`)
647754
- authentication error (`AUTH`) - incorrect signature, 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).
648755
- 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.
649-
- incorrect message body size (`SIZE`).
756+
- sent message is too large (> 15968) to be delivered (`LARGE_MSG`).
650757
- internal server error (`INTERNAL`).
651758

652759
The syntax for error responses:
653760

654761
```abnf
655762
error = %s"ERR" SP errorType
656-
errorType = %s"BLOCK" / %s"CMD" SP cmdError / %s"AUTH" / %s"SIZE" /%s"INTERNAL"
657-
cmdError = %s"SYNTAX" / %s"PROHIBITED" / %s"KEY_SIZE" / %s"NO_AUTH" / %s"HAS_AUTH" / %s"NO_QUEUE"
763+
errorType = %s"BLOCK" / %s"SESSION" / %s"CMD" SP cmdError / %s"AUTH" / %s"LARGE_MSG" /%s"INTERNAL"
764+
cmdError = %s"SYNTAX" / %s"PROHIBITED" / %s"NO_AUTH" / %s"HAS_AUTH" / %s"NO_QUEUE"
658765
```
659766

660767
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 perform signature verification even when the queue does not exist on the server or the signature of different size is sent, using any RSA key with the same size as the signature size).

0 commit comments

Comments
 (0)