diff --git a/protocol/simplex-messaging.md b/protocol/simplex-messaging.md index 0b4edd183..b394e9888 100644 --- a/protocol/simplex-messaging.md +++ b/protocol/simplex-messaging.md @@ -566,15 +566,15 @@ This command is sent to the server by the sender both to confirm the queue after ```abnf send = %s"SEND " smpEncMessage -smpEncMessage = smpPubHeader sentMsgBody ; message up to 16078 bytes +smpEncMessage = smpPubHeader sentMsgBody ; message up to 16088 bytes smpPubHeader = smpClientVersion ("1" senderPublicDhKey / "0") smpClientVersion = word16 senderPublicDhKey = length x509encoded ; sender's Curve25519 public key to agree DH secret for E2E encryption in this queue ; it is only sent in confirmation message x509encoded = -sentMsgBody = 16030*16030 OCTET -; E2E-encrypted smpClientMessage padded to 16030 bytes before encryption +sentMsgBody = 16032*16032 OCTET +; E2E-encrypted smpClientMessage padded to 16032 bytes before encryption word16 = 2*2 OCTET ``` @@ -593,14 +593,14 @@ Until the queue is secured, the server should accept any number of unsigned mess The body should be encrypted with the recipient's "public" key (`EK`); once decrypted it must have this format: ```abnf -sentMsgBody = +sentMsgBody = smpClientMessage = smpPrivHeader clientMsgBody smpPrivHeader = emptyHeader / smpConfirmationHeader emptyHeader = " " smpConfirmationHeader = %s"K" senderKey senderKey = length x509encoded ; the sender's Ed25519 or Ed448 public key to sign SEND commands for this queue -clientMsgBody = *OCTET ; up to 16012 in case of emptyHeader +clientMsgBody = *OCTET ; up to 16016 in case of emptyHeader ``` `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. @@ -610,16 +610,16 @@ SMP transmission structure for sent messages: ``` ------- transmission (= 16384 bytes) 2 | originalLength - 286- | signature sessionId corrId queueId %s"SEND" SP (1+114 + 1+32? + 1+32 + 1+24 + 4+1 = 210) - ....... smpEncMessage (= 16078 bytes = 16384 - 306 bytes) + 276- | signature sessionId corrId queueId %s"SEND" SP (1+114 + 1+32? + 1+32 + 1+24 + 4+1 = 210) + ....... smpEncMessage (= 16088 bytes = 16384 - 296 bytes) 8- | smpPubHeader (for messages it is only version and '0' to mean "no DH key" = 3 bytes) 24 | nonce for smpClientMessage 16 | auth tag for smpClientMessage - ------- smpClientMessage (E2E encrypted, = 16030 bytes = 16078 - 48) + ------- smpClientMessage (E2E encrypted, = 16032 bytes = 16088 - 48) 2 | originalLength - 8- | smpPrivHeader + 12- | smpPrivHeader ....... - | clientMsgBody (<= 16020 bytes = 16030 - 10) + | clientMsgBody (<= 16016 bytes = 16032 - 14) ....... 0+ | smpClientMessage pad ------- smpClientMessage end @@ -634,31 +634,34 @@ SMP transmission structure for received messages: ``` ------- transmission (= 16384 bytes) 2 | originalLength - 286- | signature sessionId corrId queueId %s"MSG" SP msgId timestamp (1+114 + 1+32? + 1+32 + 1+24 + 3+1 + 24+1 + 8 = 243) + 276- | signature sessionId corrId queueId %s"MSG" SP msgId timestamp (1+114 + 1+32? + 1+32 + 1+24 + 3+1 + 24+1 + 8 = 243) 16 | auth tag (msgId is used as nonce) - ------- serverEncryptedMsg (= 16080 bytes = 16384 - 304 bytes) + ------- serverEncryptedMsg (= 16090 bytes = 16384 - 294 bytes) 2 | originalLength - ....... smpEncMessage (= 16078 bytes = 16080 - 2 bytes) - 8- | smpPubHeader (empty header for the message) + ....... smpEncMessage (= 16088 bytes = 16090 - 2 bytes) + 16- | smpPubHeader (empty header for the message) 24 | nonce for smpClientMessage 16 | auth tag for smpClientMessage - ------- smpClientMessage (E2E encrypted, = 16030 bytes = 16078 - 48 bytes) + ------- smpClientMessage (E2E encrypted, = 16032 bytes = 16088 - 56 bytes) 2 | originalLength - 8- | smpPrivHeader (empty header for the message) - ....... clientMsgBody (<= 16020 bytes = 16030 - 10) + 16- | smpPrivHeader (empty header for the message) + ....... clientMsgBody (<= 16016 bytes = 16032 - 16) -- TODO move internal structure (below) to agent protocol - 8- | agentPublicHeader (the size is for user messages post handshake, without E2E X3DH keys - it is version and 'M' for the messages - 3 bytes in total) - ....... E2E double-ratchet encrypted (= 16012 bytes = 16020 - 8) - 88 | double-ratchet header (actual size is 69 bytes, the rest is reserved) - 16 | double-ratchet header auth tag - 16 | double-ratchet header iv + 20- | agentPublicHeader (the size is for user messages post handshake, without E2E X3DH keys - it is version and 'M' for the messages - 3 bytes in total) + ....... E2E double-ratchet encrypted (<= 15996 bytes = 16016 - 20) + 1 | encoded double ratchet header length (it is 123 now) + 123 | encoded double ratchet header, including: + 2 | version + 16 | double-ratchet header iv + 16 | double-ratchet header auth tag + 1+88 | double-ratchet header (actual size is 69 bytes, the rest is reserved) 16 | message auth tag (IV generated from chain ratchet) - ------- encrypted agent message (= 15876 bytes = 16012 - 136) + ------- encrypted agent message (= 15856 bytes = 15996 - 140) 2 | originalLength - 84 (41) | agentHeader (8 + 1+32) + 64- | agentHeader (the actual size is 41 = 8 + 1+32) 2 | %s"MM" ....... - | application message (<= 15788 bytes = 15876 - 88) + | application message (<= 15788 bytes = 15856 - 68) ....... 0+ | encrypted agent message pad ------- encrypted agent message end @@ -706,7 +709,7 @@ The server must deliver messages to all subscribed simplex queues on the current ```abnf message = %s"MSG " msgId SP timestamp SP encryptedMsgBody encryptedMsgBody = ; server-encrypted padded sent msgBody -paddedSentMsgBody = ; maxMessageLength = 16078 +paddedSentMsgBody = ; maxMessageLength = 16088 msgId = length 24*24OCTET timestamp = 8*8OCTET ``` @@ -759,7 +762,7 @@ No further messages should be delivered to unsubscribed transport connection. - transmission has no required queue ID (`NO_QUEUE`) - 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). - 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. -- sent message is too large (> 16078) to be delivered (`LARGE_MSG`). +- sent message is too large (> 16088) to be delivered (`LARGE_MSG`). - internal server error (`INTERNAL`). The syntax for error responses: diff --git a/src/Simplex/Messaging/Agent.hs b/src/Simplex/Messaging/Agent.hs index 954317241..a57a2e5c5 100644 --- a/src/Simplex/Messaging/Agent.hs +++ b/src/Simplex/Messaging/Agent.hs @@ -458,18 +458,20 @@ runSmpQueueMsgDelivery c@AgentClient {subQ} connId sq = do withStore (\st -> E.try $ getPendingMsgData st connId msgId) >>= \case Left (e :: E.SomeException) -> notify $ MERR mId (INTERNAL $ show e) - Right (rq_, (msgType, msgBody)) -> do - withRetryInterval ri $ \loop -> do + Right (rq_, (msgType, msgBody)) -> + withRetryInterval ri $ \loop -> tryError (sendAgentMessage c sq msgBody) >>= \case - Left e -> case e of - SMP SMP.QUOTA -> loop - SMP SMP.AUTH -> case msgType of - HELLO_ -> loop - REPLY_ -> notify $ ERR e - A_MSG_ -> notify $ MERR mId e - SMP {} -> notify $ MERR mId e - CMD {} -> notify $ MERR mId e - _ -> loop + Left e -> do + case e of + SMP SMP.QUOTA -> loop + SMP SMP.AUTH -> case msgType of + HELLO_ -> loop + REPLY_ -> notify (ERR e) >> delMsg msgId + A_MSG_ -> notify (MERR mId e) >> delMsg msgId + SMP (SMP.CMD _) -> notify (MERR mId e) >> delMsg msgId + SMP SMP.LARGE_MSG -> notify (MERR mId e) >> delMsg msgId + SMP {} -> notify (MERR mId e) >> loop + _ -> loop Right () -> do case msgType of HELLO_ -> do @@ -483,8 +485,10 @@ runSmpQueueMsgDelivery c@AgentClient {subQ} connId sq = do _ -> createReplyQueue c connId sq A_MSG_ -> notify $ SENT mId _ -> pure () - withStore $ \st -> deleteMsg st connId msgId + delMsg msgId where + delMsg :: InternalId -> m () + delMsg msgId = withStore $ \st -> deleteMsg st connId msgId notify :: ACommand 'Agent -> m () notify cmd = atomically $ writeTBQueue subQ ("", connId, cmd) diff --git a/src/Simplex/Messaging/Agent/Protocol.hs b/src/Simplex/Messaging/Agent/Protocol.hs index 893be29c7..b274c696d 100644 --- a/src/Simplex/Messaging/Agent/Protocol.hs +++ b/src/Simplex/Messaging/Agent/Protocol.hs @@ -161,7 +161,7 @@ e2eEncConnInfoLength :: Int e2eEncConnInfoLength = 14848 e2eEncUserMsgLength :: Int -e2eEncUserMsgLength = 15876 +e2eEncUserMsgLength = 15856 -- | Raw (unparsed) SMP agent protocol transmission. type ARawTransmission = (ByteString, ByteString, ByteString) @@ -304,9 +304,9 @@ data AgentMsgEnvelope encAgentMessage :: ByteString } | AgentInvitation -- the connInfo in contactInvite is only encrypted with per-queue E2E, not with double ratchet, - { agentVersion :: !Version, - connReq :: !(ConnectionRequestUri 'CMInvitation), - connInfo :: !ByteString -- this message is only encrypted with per-queue E2E, not with double ratchet, + { agentVersion :: Version, + connReq :: (ConnectionRequestUri 'CMInvitation), + connInfo :: ByteString -- this message is only encrypted with per-queue E2E, not with double ratchet, } deriving (Show) diff --git a/src/Simplex/Messaging/Crypto/Ratchet.hs b/src/Simplex/Messaging/Crypto/Ratchet.hs index 02d1f1783..9e6ca20e7 100644 --- a/src/Simplex/Messaging/Crypto/Ratchet.hs +++ b/src/Simplex/Messaging/Crypto/Ratchet.hs @@ -285,22 +285,22 @@ instance AlgorithmI a => Encoding (MsgHeader a) where data EncMessageHeader = EncMessageHeader { ehVersion :: Version, - ehBody :: ByteString, + ehIV :: IV, ehAuthTag :: AuthTag, - ehIV :: IV + ehBody :: ByteString } instance Encoding EncMessageHeader where - smpEncode EncMessageHeader {ehVersion, ehBody, ehAuthTag, ehIV} = - smpEncode (ehVersion, ehBody, ehAuthTag, ehIV) + smpEncode EncMessageHeader {ehVersion, ehIV, ehAuthTag, ehBody} = + smpEncode (ehVersion, ehIV, ehAuthTag, ehBody) smpP = do - (ehVersion, ehBody, ehAuthTag, ehIV) <- smpP - pure EncMessageHeader {ehVersion, ehBody, ehAuthTag, ehIV} + (ehVersion, ehIV, ehAuthTag, ehBody) <- smpP + pure EncMessageHeader {ehVersion, ehIV, ehAuthTag, ehBody} data EncRatchetMessage = EncRatchetMessage { emHeader :: ByteString, - emBody :: ByteString, - emAuthTag :: AuthTag + emAuthTag :: AuthTag, + emBody :: ByteString } instance Encoding EncRatchetMessage where diff --git a/src/Simplex/Messaging/Encoding.hs b/src/Simplex/Messaging/Encoding.hs index 9fc1a59fe..a0abca35e 100644 --- a/src/Simplex/Messaging/Encoding.hs +++ b/src/Simplex/Messaging/Encoding.hs @@ -108,10 +108,6 @@ instance Encoding SystemTime where smpEncode = smpEncode . systemSeconds smpP = MkSystemTime <$> smpP <*> pure 0 -instance (Encoding a, Encoding b) => Encoding (a, b) where - smpEncode (a, b) = smpEncode a <> smpEncode b - smpP = (,) <$> smpP <*> smpP - -- lists encode/parse as a sequence of items prefixed with list length (as 1 byte) smpEncodeList :: Encoding a => [a] -> ByteString smpEncodeList xs = B.cons (lenEncode $ length xs) . B.concat $ map smpEncode xs @@ -130,6 +126,10 @@ instance Encoding a => Encoding (L.NonEmpty a) where 0 -> fail "empty list" n -> L.fromList <$> A.count n smpP +instance (Encoding a, Encoding b) => Encoding (a, b) where + smpEncode (a, b) = smpEncode a <> smpEncode b + smpP = (,) <$> smpP <*> smpP + instance (Encoding a, Encoding b, Encoding c) => Encoding (a, b, c) where smpEncode (a, b, c) = smpEncode a <> smpEncode b <> smpEncode c smpP = (,,) <$> smpP <*> smpP <*> smpP diff --git a/src/Simplex/Messaging/Protocol.hs b/src/Simplex/Messaging/Protocol.hs index 0657c35c7..c1e688001 100644 --- a/src/Simplex/Messaging/Protocol.hs +++ b/src/Simplex/Messaging/Protocol.hs @@ -118,14 +118,14 @@ smpClientVRange :: VersionRange smpClientVRange = mkVersionRange 1 smpClientVersion maxMessageLength :: Int -maxMessageLength = 16078 +maxMessageLength = 16088 -- it is shorter to allow per-queue e2e encryption DH key in the "public" header e2eEncConfirmationLength :: Int -e2eEncConfirmationLength = 15942 +e2eEncConfirmationLength = 15936 e2eEncMessageLength :: Int -e2eEncMessageLength = 16030 +e2eEncMessageLength = 16032 -- | SMP protocol clients data Party = Recipient | Sender | Notifier