Skip to content

Commit 0d8a1a2

Browse files
agent: encrypt messages on delivery (#1446)
* agent: save message body once (plan, schema) * split * new type * bs * encrypt on delivery * schema * fix test * check pad size * rename --------- Co-authored-by: Evgeny Poberezkin <[email protected]>
1 parent bd97cb0 commit 0d8a1a2

File tree

11 files changed

+154
-37
lines changed

11 files changed

+154
-37
lines changed

simplexmq.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ library
198198
Simplex.Messaging.Agent.Store.SQLite.Migrations.M20240930_ntf_tokens_to_delete
199199
Simplex.Messaging.Agent.Store.SQLite.Migrations.M20241007_rcv_queues_last_broker_ts
200200
Simplex.Messaging.Agent.Store.SQLite.Migrations.M20241224_ratchet_e2e_snd_params
201+
Simplex.Messaging.Agent.Store.SQLite.Migrations.M20250203_msg_bodies
201202
if !flag(client_library)
202203
exposed-modules:
203204
Simplex.FileTransfer.Client.Main

src/Simplex/Messaging/Agent.hs

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1379,6 +1379,7 @@ enqueueMessage c cData sq msgFlags aMessage =
13791379
ExceptT $ fmap fst . runIdentity <$> enqueueMessageB c (Identity (Right (cData, [sq], Nothing, msgFlags, aMessage)))
13801380
{-# INLINE enqueueMessage #-}
13811381

1382+
-- TODO [save once] IntMap of msg bodies.
13821383
-- this function is used only for sending messages in batch, it returns the list of successes to enqueue additional deliveries
13831384
enqueueMessageB :: forall t. Traversable t => AgentClient -> t (Either AgentErrorType (ConnData, NonEmpty SndQueue, Maybe PQEncryption, MsgFlags, AMessage)) -> AM' (t (Either AgentErrorType ((AgentMsgId, PQEncryption), Maybe (ConnData, [SndQueue], AgentMsgId))))
13841385
enqueueMessageB c reqs = do
@@ -1391,19 +1392,21 @@ enqueueMessageB c reqs = do
13911392
where
13921393
storeSentMsg :: DB.Connection -> AgentConfig -> (ConnData, NonEmpty SndQueue, Maybe PQEncryption, MsgFlags, AMessage) -> IO (Either AgentErrorType ((ConnData, NonEmpty SndQueue, Maybe PQEncryption, MsgFlags, AMessage), InternalId, PQEncryption))
13931394
storeSentMsg db cfg req@(cData@ConnData {connId}, sq :| _, pqEnc_, msgFlags, aMessage) = fmap (first storeError) $ runExceptT $ do
1394-
let AgentConfig {smpAgentVRange, e2eEncryptVRange} = cfg
1395+
let AgentConfig {e2eEncryptVRange} = cfg
13951396
internalTs <- liftIO getCurrentTime
13961397
(internalId, internalSndId, prevMsgHash) <- ExceptT $ updateSndIds db connId
13971398
let privHeader = APrivHeader (unSndId internalSndId) prevMsgHash
13981399
agentMsg = AgentMessage privHeader aMessage
13991400
agentMsgStr = smpEncode agentMsg
14001401
internalHash = C.sha256Hash agentMsgStr
14011402
currentE2EVersion = maxVersion e2eEncryptVRange
1402-
(encAgentMessage, pqEnc) <- agentRatchetEncrypt db cData agentMsgStr e2eEncAgentMsgLength pqEnc_ currentE2EVersion
1403-
let agentVersion = maxVersion smpAgentVRange
1404-
msgBody = smpEncode $ AgentMsgEnvelope {agentVersion, encAgentMessage}
1405-
msgType = agentMessageType agentMsg
1406-
msgData = SndMsgData {internalId, internalSndId, internalTs, msgType, msgFlags, msgBody, pqEncryption = pqEnc, internalHash, prevMsgHash}
1403+
-- TODO [save once] Save single MsgBody / enveloped body agentMsgStr (outside of withStoreBatch ... storeSentMsg).
1404+
-- TODO Link messages to it, save encryption data per message.
1405+
-- TODO 'msg_body' field is not nullable - use default empty strings?
1406+
(mek, paddedLen, pqEnc) <- agentRatchetEncryptHeader db cData e2eEncAgentMsgLength pqEnc_ currentE2EVersion
1407+
withExceptT (SEAgentError . cryptoError) $ CR.rcCheckCanPad paddedLen agentMsgStr
1408+
let msgType = agentMessageType agentMsg
1409+
msgData = SndMsgData {internalId, internalSndId, internalTs, msgType, msgFlags, msgBody = agentMsgStr, pqEncryption = pqEnc, internalHash, prevMsgHash, encryptKey_ = Just mek, paddedLen_ = Just paddedLen}
14071410
liftIO $ createSndMsg db connId msgData
14081411
liftIO $ createSndMsgDelivery db connId sq internalId
14091412
pure (req, internalId, pqEnc)
@@ -1451,7 +1454,7 @@ runSmpQueueMsgDelivery c@AgentClient {subQ} ConnData {connId} sq@SndQueue {userI
14511454
liftIO $ throwWhenNoDelivery c sq
14521455
atomically $ beginAgentOperation c AOSndNetwork
14531456
withWork c doWork (\db -> getPendingQueueMsg db connId sq) $
1454-
\(rq_, PendingMsgData {msgId, msgType, msgBody, pqEncryption, msgFlags, msgRetryState, internalTs}) -> do
1457+
\(rq_, PendingMsgData {msgId, msgType, msgBody, pqEncryption, msgFlags, msgRetryState, internalTs, encryptKey_, paddedLen_}) -> do
14551458
atomically $ endAgentOperation c AOMsgDelivery -- this operation begins in submitPendingMsg
14561459
let mId = unId msgId
14571460
ri' = maybe id updateRetryInterval2 msgRetryState ri
@@ -1461,7 +1464,15 @@ runSmpQueueMsgDelivery c@AgentClient {subQ} ConnData {connId} sq@SndQueue {userI
14611464
resp <- tryError $ case msgType of
14621465
AM_CONN_INFO -> sendConfirmation c sq msgBody
14631466
AM_CONN_INFO_REPLY -> sendConfirmation c sq msgBody
1464-
_ -> sendAgentMessage c sq msgFlags msgBody
1467+
_ -> case (encryptKey_, paddedLen_) of
1468+
(Nothing, Nothing) -> sendAgentMessage c sq msgFlags msgBody
1469+
(Just mek, Just paddedLen) -> do
1470+
AgentConfig {smpAgentVRange} <- asks config
1471+
encAgentMessage <- liftError cryptoError $ CR.rcEncryptMsg mek paddedLen msgBody
1472+
let agentVersion = maxVersion smpAgentVRange
1473+
msgBody' = smpEncode $ AgentMsgEnvelope {agentVersion, encAgentMessage}
1474+
sendAgentMessage c sq msgFlags msgBody'
1475+
_ -> throwE $ INTERNAL "runSmpQueueMsgDelivery: missing encryption data"
14651476
case resp of
14661477
Left e -> do
14671478
let err = if msgType == AM_A_MSG_ then MERR mId e else ERR e
@@ -1833,7 +1844,7 @@ deleteConnQueues c waitDelivery ntf rqs = do
18331844
deleteQueueRecs rs = do
18341845
maxErrs <- asks $ deleteErrorCount . config
18351846
rs' <- rights <$> withStoreBatch' c (\db -> map (deleteQueueRec db maxErrs) rs)
1836-
let delQ ((rq, _), err_) = (qConnId rq,qServer rq,queueId rq,) <$> err_
1847+
let delQ ((rq, _), err_) = (qConnId rq,qServer rq,queueId rq,) <$> err_
18371848
delQs_ = L.nonEmpty $ mapMaybe delQ rs'
18381849
forM_ delQs_ $ \delQs -> notify ("", "", AEvt SAEConn $ DEL_RCVQS delQs)
18391850
pure $ map fst rs'
@@ -2952,7 +2963,7 @@ storeConfirmation c cData@ConnData {connId, pqSupport, connAgentVersion = v} sq
29522963
(encConnInfo, pqEncryption) <- agentRatchetEncrypt db cData agentMsgStr e2eEncConnInfoLength (Just pqEnc) currentE2EVersion
29532964
let msgBody = smpEncode $ AgentConfirmation {agentVersion = v, e2eEncryption_, encConnInfo}
29542965
msgType = agentMessageType agentMsg
2955-
msgData = SndMsgData {internalId, internalSndId, internalTs, msgType, msgBody, pqEncryption, msgFlags = SMP.MsgFlags {notification = True}, internalHash, prevMsgHash}
2966+
msgData = SndMsgData {internalId, internalSndId, internalTs, msgType, msgBody, pqEncryption, msgFlags = SMP.MsgFlags {notification = True}, internalHash, prevMsgHash, encryptKey_ = Nothing, paddedLen_ = Nothing}
29562967
liftIO $ createSndMsg db connId msgData
29572968
liftIO $ createSndMsgDelivery db connId sq internalId
29582969

@@ -2978,19 +2989,25 @@ enqueueRatchetKey c cData@ConnData {connId} sq e2eEncryption = do
29782989
let msgBody = smpEncode $ AgentRatchetKey {agentVersion, e2eEncryption, info = agentMsgStr}
29792990
msgType = agentMessageType agentMsg
29802991
-- this message is e2e encrypted with queue key, not with double ratchet
2981-
msgData = SndMsgData {internalId, internalSndId, internalTs, msgType, msgBody, pqEncryption = PQEncOff, msgFlags = SMP.MsgFlags {notification = True}, internalHash, prevMsgHash}
2992+
msgData = SndMsgData {internalId, internalSndId, internalTs, msgType, msgBody, pqEncryption = PQEncOff, msgFlags = SMP.MsgFlags {notification = True}, internalHash, prevMsgHash, encryptKey_ = Nothing, paddedLen_ = Nothing}
29822993
liftIO $ createSndMsg db connId msgData
29832994
liftIO $ createSndMsgDelivery db connId sq internalId
29842995
pure internalId
29852996

29862997
-- encoded AgentMessage -> encoded EncAgentMessage
29872998
agentRatchetEncrypt :: DB.Connection -> ConnData -> ByteString -> (VersionSMPA -> PQSupport -> Int) -> Maybe PQEncryption -> CR.VersionE2E -> ExceptT StoreError IO (ByteString, PQEncryption)
2988-
agentRatchetEncrypt db ConnData {connId, connAgentVersion = v, pqSupport} msg getPaddedLen pqEnc_ currentE2EVersion = do
2999+
agentRatchetEncrypt db cData msg getPaddedLen pqEnc_ currentE2EVersion = do
3000+
(mek, paddedLen, pqEnc) <- agentRatchetEncryptHeader db cData getPaddedLen pqEnc_ currentE2EVersion
3001+
encMsg <- withExceptT (SEAgentError . cryptoError) $ CR.rcEncryptMsg mek paddedLen msg
3002+
pure (encMsg, pqEnc)
3003+
3004+
agentRatchetEncryptHeader :: DB.Connection -> ConnData -> (VersionSMPA -> PQSupport -> Int) -> Maybe PQEncryption -> CR.VersionE2E -> ExceptT StoreError IO (CR.MsgEncryptKeyX448, Int, PQEncryption)
3005+
agentRatchetEncryptHeader db ConnData {connId, connAgentVersion = v, pqSupport} getPaddedLen pqEnc_ currentE2EVersion = do
29893006
rc <- ExceptT $ getRatchet db connId
29903007
let paddedLen = getPaddedLen v pqSupport
2991-
(encMsg, rc') <- withExceptT (SEAgentError . cryptoError) $ CR.rcEncrypt rc paddedLen msg pqEnc_ currentE2EVersion
3008+
(mek, rc') <- withExceptT (SEAgentError . cryptoError) $ CR.rcEncryptHeader rc pqEnc_ currentE2EVersion
29923009
liftIO $ updateRatchet db connId rc' CR.SMDNoChange
2993-
pure (encMsg, CR.rcSndKEM rc')
3010+
pure (mek, paddedLen, CR.rcSndKEM rc')
29943011

29953012
-- encoded EncAgentMessage -> encoded AgentMessage
29963013
agentRatchetDecrypt :: TVar ChaChaDRG -> DB.Connection -> ConnId -> ByteString -> ExceptT StoreError IO (ByteString, PQEncryption)

src/Simplex/Messaging/Agent/Store.hs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import Simplex.Messaging.Agent.Store.Common
3333
import Simplex.Messaging.Agent.Store.Interface (DBOpts, appMigrations, createDBStore)
3434
import Simplex.Messaging.Agent.Store.Shared (MigrationConfirmation (..), MigrationError (..))
3535
import qualified Simplex.Messaging.Crypto as C
36-
import Simplex.Messaging.Crypto.Ratchet (PQEncryption, PQSupport, RatchetX448)
36+
import Simplex.Messaging.Crypto.Ratchet (MsgEncryptKeyX448, PQEncryption, PQSupport, RatchetX448)
3737
import Simplex.Messaging.Encoding.String
3838
import Simplex.Messaging.Protocol
3939
( MsgBody,
@@ -542,7 +542,9 @@ data SndMsgData = SndMsgData
542542
msgBody :: MsgBody,
543543
pqEncryption :: PQEncryption,
544544
internalHash :: MsgHash,
545-
prevMsgHash :: MsgHash
545+
prevMsgHash :: MsgHash,
546+
encryptKey_ :: Maybe MsgEncryptKeyX448,
547+
paddedLen_ :: Maybe Int
546548
}
547549

548550
data SndMsg = SndMsg
@@ -560,7 +562,9 @@ data PendingMsgData = PendingMsgData
560562
msgBody :: MsgBody,
561563
pqEncryption :: PQEncryption,
562564
msgRetryState :: Maybe RI2State,
563-
internalTs :: InternalTs
565+
internalTs :: InternalTs,
566+
encryptKey_ :: Maybe MsgEncryptKeyX448,
567+
paddedLen_ :: Maybe Int
564568
}
565569
deriving (Show)
566570

src/Simplex/Messaging/Agent/Store/AgentStore.hs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -844,18 +844,18 @@ getPendingQueueMsg db connId SndQueue {dbQueueId} =
844844
DB.query
845845
db
846846
[sql|
847-
SELECT m.msg_type, m.msg_flags, m.msg_body, m.pq_encryption, m.internal_ts, s.retry_int_slow, s.retry_int_fast
847+
SELECT m.msg_type, m.msg_flags, m.msg_body, m.pq_encryption, m.internal_ts, s.retry_int_slow, s.retry_int_fast, s.msg_encrypt_key, s.padded_msg_len
848848
FROM messages m
849849
JOIN snd_messages s ON s.conn_id = m.conn_id AND s.internal_id = m.internal_id
850850
WHERE m.conn_id = ? AND m.internal_id = ?
851851
|]
852852
(connId, msgId)
853853
err = SEInternal $ "msg delivery " <> bshow msgId <> " returned []"
854-
pendingMsgData :: (AgentMessageType, Maybe MsgFlags, MsgBody, PQEncryption, InternalTs, Maybe Int64, Maybe Int64) -> PendingMsgData
855-
pendingMsgData (msgType, msgFlags_, msgBody, pqEncryption, internalTs, riSlow_, riFast_) =
854+
pendingMsgData :: (AgentMessageType, Maybe MsgFlags, MsgBody, PQEncryption, InternalTs, Maybe Int64, Maybe Int64, Maybe CR.MsgEncryptKeyX448, Maybe Int) -> PendingMsgData
855+
pendingMsgData (msgType, msgFlags_, msgBody, pqEncryption, internalTs, riSlow_, riFast_, encryptKey_, paddedLen_) =
856856
let msgFlags = fromMaybe SMP.noMsgFlags msgFlags_
857857
msgRetryState = RI2State <$> riSlow_ <*> riFast_
858-
in PendingMsgData {msgId, msgType, msgFlags, msgBody, pqEncryption, msgRetryState, internalTs}
858+
in PendingMsgData {msgId, msgType, msgFlags, msgBody, pqEncryption, msgRetryState, internalTs, encryptKey_, paddedLen_}
859859
markMsgFailed msgId = DB.execute db "UPDATE snd_message_deliveries SET failed = 1 WHERE conn_id = ? AND internal_id = ?" (connId, msgId)
860860

861861
getWorkItem :: Show i => ByteString -> IO (Maybe i) -> (i -> IO (Either StoreError a)) -> (i -> IO ()) -> IO (Either StoreError (Maybe a))
@@ -997,6 +997,7 @@ deleteDeliveredSndMsg db connId msgId = do
997997
cnt <- countPendingSndDeliveries_ db connId msgId
998998
when (cnt == 0) $ deleteMsg db connId msgId
999999

1000+
-- TODO [save once] Delete from shared message bodies if no deliveries reference it. (`when (cnt == 0)`)
10001001
deleteSndMsgDelivery :: DB.Connection -> ConnId -> SndQueue -> InternalId -> Bool -> IO ()
10011002
deleteSndMsgDelivery db connId SndQueue {dbQueueId} msgId keepForReceipt = do
10021003
DB.execute
@@ -2206,11 +2207,11 @@ insertSndMsgDetails_ dbConn connId SndMsgData {..} =
22062207
dbConn
22072208
[sql|
22082209
INSERT INTO snd_messages
2209-
( conn_id, internal_snd_id, internal_id, internal_hash, previous_msg_hash)
2210+
( conn_id, internal_snd_id, internal_id, internal_hash, previous_msg_hash, msg_encrypt_key, padded_msg_len)
22102211
VALUES
2211-
(?,?,?,?,?)
2212+
(?,?,?,?,?,?,?)
22122213
|]
2213-
(connId, internalSndId, internalId, Binary internalHash, Binary prevMsgHash)
2214+
(connId, internalSndId, internalId, Binary internalHash, Binary prevMsgHash, encryptKey_, paddedLen_)
22142215

22152216
updateSndMsgHash :: DB.Connection -> ConnId -> InternalSndId -> MsgHash -> IO ()
22162217
updateSndMsgHash db connId internalSndId internalHash =

src/Simplex/Messaging/Agent/Store/SQLite/Migrations.hs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ import Simplex.Messaging.Agent.Store.SQLite.Migrations.M20240702_servers_stats
6666
import Simplex.Messaging.Agent.Store.SQLite.Migrations.M20240930_ntf_tokens_to_delete
6767
import Simplex.Messaging.Agent.Store.SQLite.Migrations.M20241007_rcv_queues_last_broker_ts
6868
import Simplex.Messaging.Agent.Store.SQLite.Migrations.M20241224_ratchet_e2e_snd_params
69+
import Simplex.Messaging.Agent.Store.SQLite.Migrations.M20250203_msg_bodies
6970
import Simplex.Messaging.Agent.Store.Shared
7071
import Simplex.Messaging.Encoding.String
7172
import Simplex.Messaging.Transport.Client (TransportHost)
@@ -108,7 +109,8 @@ schemaMigrations =
108109
("m20240702_servers_stats", m20240702_servers_stats, Just down_m20240702_servers_stats),
109110
("m20240930_ntf_tokens_to_delete", m20240930_ntf_tokens_to_delete, Just down_m20240930_ntf_tokens_to_delete),
110111
("m20241007_rcv_queues_last_broker_ts", m20241007_rcv_queues_last_broker_ts, Just down_m20241007_rcv_queues_last_broker_ts),
111-
("m20241224_ratchet_e2e_snd_params", m20241224_ratchet_e2e_snd_params, Just down_m20241224_ratchet_e2e_snd_params)
112+
("m20241224_ratchet_e2e_snd_params", m20241224_ratchet_e2e_snd_params, Just down_m20241224_ratchet_e2e_snd_params),
113+
("m20250203_msg_bodies", m20250203_msg_bodies, Just down_m20250203_msg_bodies)
112114
]
113115

114116
-- | The list of migrations in ascending order by date
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{-# LANGUAGE QuasiQuotes #-}
2+
3+
module Simplex.Messaging.Agent.Store.SQLite.Migrations.M20250203_msg_bodies where
4+
5+
import Database.SQLite.Simple (Query)
6+
import Database.SQLite.Simple.QQ (sql)
7+
8+
m20250203_msg_bodies :: Query
9+
m20250203_msg_bodies =
10+
[sql|
11+
ALTER TABLE snd_messages ADD COLUMN msg_encrypt_key BLOB;
12+
ALTER TABLE snd_messages ADD COLUMN padded_msg_len INTEGER;
13+
14+
15+
-- CREATE TABLE msg_bodies (
16+
-- msg_body_id INTEGER PRIMARY KEY,
17+
-- msg_body BLOB NOT NULL DEFAULT x''
18+
-- )
19+
20+
-- ALTER TABLE snd_messages ADD COLUMN msg_body_id INTEGER REFERENCES msg_bodies ON DELETE CASCADE;
21+
22+
-- fkey to msg_bodies
23+
-- on each delivery check if other deliveries reference the same msg_body_id, if not delete it
24+
|]
25+
26+
down_m20250203_msg_bodies :: Query
27+
down_m20250203_msg_bodies =
28+
[sql|
29+
ALTER TABLE snd_messages DROP COLUMN msg_encrypt_key;
30+
ALTER TABLE snd_messages DROP COLUMN padded_msg_len;
31+
32+
33+
-- ALTER TABLE snd_messages DROP COLUMN msg_body_id;
34+
35+
-- DROP TABLE msg_bodies;
36+
|]

src/Simplex/Messaging/Agent/Store/SQLite/Migrations/agent_schema.sql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ CREATE TABLE snd_messages(
127127
retry_int_fast INTEGER,
128128
rcpt_internal_id INTEGER,
129129
rcpt_status TEXT,
130+
msg_encrypt_key BLOB,
131+
padded_msg_len INTEGER,
130132
PRIMARY KEY(conn_id, internal_snd_id),
131133
FOREIGN KEY(conn_id, internal_id) REFERENCES messages
132134
ON DELETE CASCADE

src/Simplex/Messaging/Crypto.hs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ module Simplex.Messaging.Crypto
169169
sha512Hash,
170170

171171
-- * Message padding / un-padding
172+
canPad,
172173
pad,
173174
unPad,
174175

@@ -1010,6 +1011,11 @@ decryptAEADNoPad aesKey iv ad msg (AuthTag tag) = do
10101011
maxMsgLen :: Int
10111012
maxMsgLen = 2 ^ (16 :: Int) - 3
10121013

1014+
canPad :: Int -> Int -> Bool
1015+
canPad msgLen paddedLen = msgLen <= maxMsgLen && padLen >= 0
1016+
where
1017+
padLen = paddedLen - msgLen - 2
1018+
10131019
pad :: ByteString -> Int -> Either CryptoError ByteString
10141020
pad msg paddedLen
10151021
| len <= maxMsgLen && padLen >= 0 = Right $ encodeWord16 (fromIntegral len) <> msg <> B.replicate padLen '#'

0 commit comments

Comments
 (0)