From ef4d4c9e16abfaca32f1b9a95d50082b2da0bf06 Mon Sep 17 00:00:00 2001 From: JRoberts <8711996+jr-simplex@users.noreply.github.com> Date: Wed, 22 Jun 2022 20:32:32 +0400 Subject: [PATCH] ntf: remove notification subscription (#417) --- protocol/simplex-messaging.md | 41 ++++++++++++++++--- .../Messaging/Notifications/Server/Store.hs | 1 + src/Simplex/Messaging/Protocol.hs | 6 +++ src/Simplex/Messaging/Server.hs | 6 +++ src/Simplex/Messaging/Server/QueueStore.hs | 1 + .../Messaging/Server/QueueStore/STM.hs | 8 ++++ src/Simplex/Messaging/Server/StoreLog.hs | 8 ++++ tests/ServerTests.hs | 10 ++++- 8 files changed, 75 insertions(+), 6 deletions(-) diff --git a/protocol/simplex-messaging.md b/protocol/simplex-messaging.md index ecd108094..e48f31b68 100644 --- a/protocol/simplex-messaging.md +++ b/protocol/simplex-messaging.md @@ -24,6 +24,7 @@ - [Subscribe to queue](#subscribe-to-queue) - [Secure queue command](#secure-queue-command) - [Enable notifications command](#enable-notifications-command) + - [Disable notifications command](#disable-notifications-command) - [Acknowledge message delivery](#acknowledge-message-delivery) - [Suspend queue](#suspend-queue) - [Delete queue](#delete-queue) @@ -355,6 +356,7 @@ 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). +- `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). @@ -401,7 +403,7 @@ Commands syntax below is provided using [ABNF][8] with [case-sensitive strings e ```abnf smpCommand = ping / recipientCmd / send / subscribeNotifications / serverMsg -recipientCmd = create / subscribe / secure / enableNotifications / +recipientCmd = create / subscribe / secure / enableNotifications / disableNotifications / acknowledge / suspend / delete serverMsg = queueIds / message / notifierId / messageNotification / unsubscribed / ok / error @@ -502,22 +504,42 @@ Once the queue is secured only signed messages can be sent to it. 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. ```abnf -enableNotifications = %s"NKEY " notifierKey +enableNotifications = %s"NKEY " notifierKey recipientNotificationDhPublicKey notifierKey = length x509encoded ; the notifier's Ed25519 or Ed448 public key public key to verify NSUB command for this queue + +recipientNotificationDhPublicKey = length x509encoded +; the recipient's Curve25519 key for DH exchange to derive the secret +; that the server will use to encrypt notification metadata (encryptedNMsgMeta in NMSG) +; 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: ```abnf -notifierId = %s"NID " notifierId +notifierId = %s"NID " notifierId srvNotificationDhPublicKey notifierId = length *OCTET ; 16-24 bytes +srvNotificationDhPublicKey = length x509encoded +; the server's Curve25519 key for DH exchange to derive the secret +; that the server will use to encrypt notification metadata to the recipient (encryptedNMsgMeta in NMSG) ``` This response is sent with the recipient's queue ID (the third part of the transmission). To receive the message notifications, `subscribeNotifications` command ("NSUB") must be sent signed with the notifier's key. +#### Disable notifications command + +This command is sent by the recipient to the server to remove notifier's credentials from the queue: + +```abnf +disableNotifications = %s"NDEL" +``` + +The server must respond `ok` to this command if it was successful. + +Once notifier's credentials are removed server will no longer send "NMSG" for this queue to notifier. + #### Acknowledge message delivery The recipient should send the acknowledgement of message delivery once the message was stored in the client, to notify the server that the message should be deleted: @@ -735,10 +757,19 @@ See its syntax in [Enable notifications command](#enable-notifications-command) The server must deliver message notifications to all simplex queues that were subscribed with `subscribeNotifications` command ("NSUB") on the currently open transport connection. The syntax for the message notification delivery is: ```abnf -messageNotification = %s"NMSG" +messageNotification = %s"NMSG " nmsgNonce encryptedNMsgMeta + +encryptedNMsgMeta = +; metadata E2E encrypted between server and recipient containing server's message ID and timestamp (allows extension), +; to be passed to the recipient by the notifier for them to decrypt +; with key negotiated in NKEY and NID commands using nmsgNonce + +nmsgNonce = +; nonce used by the server for encryption of message metadata, to be passed to the recipient by the notifier +; for them to use in decryption of E2E encrypted metadata ``` -Message notification does not contain any message data or meta-data, it only notifies that the message is available. +Message notification does not contain any message data or non E2E encrypted metadata. #### Subscription END notification diff --git a/src/Simplex/Messaging/Notifications/Server/Store.hs b/src/Simplex/Messaging/Notifications/Server/Store.hs index c7d9ac89f..50291f5ca 100644 --- a/src/Simplex/Messaging/Notifications/Server/Store.hs +++ b/src/Simplex/Messaging/Notifications/Server/Store.hs @@ -24,6 +24,7 @@ import Simplex.Messaging.Util (whenM, ($>>=)) data NtfStore = NtfStore { tokens :: TMap NtfTokenId NtfTknData, + -- multiple registrations exist to protect from malicious registrations if token is compromised tokenRegistrations :: TMap DeviceToken (TMap ByteString NtfTokenId), subscriptions :: TMap NtfSubscriptionId NtfSubData, tokenSubscriptions :: TMap NtfTokenId (TVar (Set NtfSubscriptionId)), diff --git a/src/Simplex/Messaging/Protocol.hs b/src/Simplex/Messaging/Protocol.hs index f9ed5e321..6bda98677 100644 --- a/src/Simplex/Messaging/Protocol.hs +++ b/src/Simplex/Messaging/Protocol.hs @@ -221,6 +221,7 @@ data Command (p :: Party) where SUB :: Command Recipient KEY :: SndPublicVerifyKey -> Command Recipient NKEY :: NtfPublicVerifyKey -> RcvNtfPublicDhKey -> Command Recipient + NDEL :: Command Recipient GET :: Command Recipient -- ACK v1 has to be supported for encoding/decoding -- ACK :: Command Recipient @@ -303,6 +304,7 @@ data CommandTag (p :: Party) where SUB_ :: CommandTag Recipient KEY_ :: CommandTag Recipient NKEY_ :: CommandTag Recipient + NDEL_ :: CommandTag Recipient GET_ :: CommandTag Recipient ACK_ :: CommandTag Recipient OFF_ :: CommandTag Recipient @@ -342,6 +344,7 @@ instance PartyI p => Encoding (CommandTag p) where SUB_ -> "SUB" KEY_ -> "KEY" NKEY_ -> "NKEY" + NDEL_ -> "NDEL" GET_ -> "GET" ACK_ -> "ACK" OFF_ -> "OFF" @@ -357,6 +360,7 @@ instance ProtocolMsgTag CmdTag where "SUB" -> Just $ CT SRecipient SUB_ "KEY" -> Just $ CT SRecipient KEY_ "NKEY" -> Just $ CT SRecipient NKEY_ + "NDEL" -> Just $ CT SRecipient NDEL_ "GET" -> Just $ CT SRecipient GET_ "ACK" -> Just $ CT SRecipient ACK_ "OFF" -> Just $ CT SRecipient OFF_ @@ -651,6 +655,7 @@ instance PartyI p => ProtocolEncoding (Command p) where SUB -> e SUB_ KEY k -> e (KEY_, ' ', k) NKEY k dhKey -> e (NKEY_, ' ', k, dhKey) + NDEL -> e NDEL_ GET -> e GET_ ACK msgId | v == 1 -> e ACK_ @@ -698,6 +703,7 @@ instance ProtocolEncoding Cmd where SUB_ -> pure SUB KEY_ -> KEY <$> _smpP NKEY_ -> NKEY <$> _smpP <*> smpP + NDEL_ -> pure NDEL GET_ -> pure GET ACK_ | v == 1 -> pure $ ACK "" diff --git a/src/Simplex/Messaging/Server.hs b/src/Simplex/Messaging/Server.hs index e21647d0c..a32fc93a3 100644 --- a/src/Simplex/Messaging/Server.hs +++ b/src/Simplex/Messaging/Server.hs @@ -334,6 +334,7 @@ client clnt@Client {subscriptions, ntfSubscriptions, rcvQ, sndQ} Server {subscri ACK msgId -> acknowledgeMsg msgId KEY sKey -> secureQueue_ st sKey NKEY nKey dhKey -> addQueueNotifier_ st nKey dhKey + NDEL -> deleteQueueNotifier_ st OFF -> suspendQueue_ st DEL -> delQueueAndMsgs st where @@ -405,6 +406,11 @@ client clnt@Client {subscriptions, ntfSubscriptions, rcvQ, sndQ} Server {subscri withLog $ \s -> logAddNotifier s queueId ntfCreds pure $ NID notifierId rcvPublicDhKey + deleteQueueNotifier_ :: QueueStore -> m (Transmission BrokerMsg) + deleteQueueNotifier_ st = do + withLog (`logDeleteNotifier` queueId) + okResp <$> atomically (deleteQueueNotifier st queueId) + suspendQueue_ :: QueueStore -> m (Transmission BrokerMsg) suspendQueue_ st = do withLog (`logDeleteQueue` queueId) diff --git a/src/Simplex/Messaging/Server/QueueStore.hs b/src/Simplex/Messaging/Server/QueueStore.hs index f059d3170..b8a2f10ad 100644 --- a/src/Simplex/Messaging/Server/QueueStore.hs +++ b/src/Simplex/Messaging/Server/QueueStore.hs @@ -39,5 +39,6 @@ class MonadQueueStore s m where getQueue :: s -> SParty p -> QueueId -> m (Either ErrorType QueueRec) secureQueue :: s -> RecipientId -> SndPublicVerifyKey -> m (Either ErrorType QueueRec) addQueueNotifier :: s -> RecipientId -> NtfCreds -> m (Either ErrorType QueueRec) + deleteQueueNotifier :: s -> RecipientId -> m (Either ErrorType ()) suspendQueue :: s -> RecipientId -> m (Either ErrorType ()) deleteQueue :: s -> RecipientId -> m (Either ErrorType ()) diff --git a/src/Simplex/Messaging/Server/QueueStore/STM.hs b/src/Simplex/Messaging/Server/QueueStore/STM.hs index 101c26251..aaf68a526 100644 --- a/src/Simplex/Messaging/Server/QueueStore/STM.hs +++ b/src/Simplex/Messaging/Server/QueueStore/STM.hs @@ -71,6 +71,14 @@ instance MonadQueueStore QueueStore STM where TM.insert nId rId notifiers pure $ Just q + deleteQueueNotifier :: QueueStore -> RecipientId -> STM (Either ErrorType ()) + deleteQueueNotifier QueueStore {queues, notifiers} rId = + withQueue rId queues $ \qVar -> do + q <- readTVar qVar + forM_ (notifier q) $ \NtfCreds {notifierId} -> TM.delete notifierId notifiers + writeTVar qVar q {notifier = Nothing} + pure $ Just () + suspendQueue :: QueueStore -> RecipientId -> STM (Either ErrorType ()) suspendQueue QueueStore {queues} rId = withQueue rId queues $ \qVar -> modifyTVar' qVar (\q -> q {status = QueueOff}) $> Just () diff --git a/src/Simplex/Messaging/Server/StoreLog.hs b/src/Simplex/Messaging/Server/StoreLog.hs index b18b57f20..49ec62c1f 100644 --- a/src/Simplex/Messaging/Server/StoreLog.hs +++ b/src/Simplex/Messaging/Server/StoreLog.hs @@ -17,6 +17,7 @@ module Simplex.Messaging.Server.StoreLog logSecureQueue, logAddNotifier, logDeleteQueue, + logDeleteNotifier, readWriteStoreLog, ) where @@ -50,6 +51,7 @@ data StoreLogRecord | SecureQueue QueueId SndPublicVerifyKey | AddNotifier QueueId NtfCreds | DeleteQueue QueueId + | DeleteNotifier QueueId instance StrEncoding QueueRec where strEncode QueueRec {recipientId, recipientKey, rcvDhSecret, senderId, senderKey, notifier} = @@ -79,12 +81,14 @@ instance StrEncoding StoreLogRecord where SecureQueue rId sKey -> strEncode (Str "SECURE", rId, sKey) AddNotifier rId ntfCreds -> strEncode (Str "NOTIFIER", rId, ntfCreds) DeleteQueue rId -> strEncode (Str "DELETE", rId) + DeleteNotifier rId -> strEncode (Str "NDELETE", rId) strP = "CREATE " *> (CreateQueue <$> strP) <|> "SECURE " *> (SecureQueue <$> strP_ <*> strP) <|> "NOTIFIER " *> (AddNotifier <$> strP_ <*> strP) <|> "DELETE " *> (DeleteQueue <$> strP) + <|> "NDELETE" *> (DeleteNotifier <$> strP) openWriteStoreLog :: FilePath -> IO (StoreLog 'WriteMode) openWriteStoreLog f = WriteStoreLog f <$> openFile f WriteMode @@ -121,6 +125,9 @@ logAddNotifier s qId ntfCreds = writeStoreLogRecord s $ AddNotifier qId ntfCreds logDeleteQueue :: StoreLog 'WriteMode -> QueueId -> IO () logDeleteQueue s = writeStoreLogRecord s . DeleteQueue +logDeleteNotifier :: StoreLog 'WriteMode -> QueueId -> IO () +logDeleteNotifier s = writeStoreLogRecord s . DeleteNotifier + readWriteStoreLog :: StoreLog 'ReadMode -> IO (Map RecipientId QueueRec, StoreLog 'WriteMode) readWriteStoreLog s@(ReadStoreLog f _) = do qs <- readQueues s @@ -151,5 +158,6 @@ readQueues (ReadStoreLog _ h) = LB.hGetContents h >>= returnResult . procStoreLo SecureQueue qId sKey -> M.adjust (\q -> q {senderKey = Just sKey}) qId m AddNotifier qId ntfCreds -> M.adjust (\q -> q {notifier = Just ntfCreds}) qId m DeleteQueue qId -> M.delete qId m + DeleteNotifier qId -> M.adjust (\q -> q {notifier = Nothing}) qId m printError :: LogParsingError -> IO () printError (e, s) = B.putStrLn $ "Error parsing log: " <> B.pack e <> " - " <> s diff --git a/tests/ServerTests.hs b/tests/ServerTests.hs index 72d87fb28..4b6c0f47c 100644 --- a/tests/ServerTests.hs +++ b/tests/ServerTests.hs @@ -625,11 +625,19 @@ testMessageNotifications (ATransport t) = Resp "" _ END <- tGet nh1 Resp "5" _ OK <- signSendRecv sh sKey ("5", sId, _SEND' "hello again") Resp "" _ (MSG mId2 _ _ msg2) <- tGet rh + Resp "5a" _ OK <- signSendRecv rh rKey ("5a", rId, ACK mId2) (dec mId2 msg2, Right "hello again") #== "delivered from queue again" Resp "" _ (NMSG _ _) <- tGet nh2 1000 `timeout` tGet @BrokerMsg nh1 >>= \case - Nothing -> return () + Nothing -> pure () Just _ -> error "nothing else should be delivered to the 1st notifier's TCP connection" + Resp "6" _ OK <- signSendRecv rh rKey ("6", rId, NDEL) + Resp "7" _ OK <- signSendRecv sh sKey ("7", sId, _SEND' "hello there") + Resp "" _ (MSG mId3 _ _ msg3) <- tGet rh + (dec mId3 msg3, Right "hello there") #== "delivered from queue again" + 1000 `timeout` tGet @BrokerMsg nh2 >>= \case + Nothing -> pure () + Just _ -> error "nothing else should be delivered to the 2nd notifier's TCP connection" testMsgExpireOnSend :: forall c. Transport c => TProxy c -> Spec testMsgExpireOnSend t =