From 02225df274a2ab332681ebadf87ccbf28396c845 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Fri, 10 Nov 2023 16:10:10 +0000 Subject: [PATCH] core: remote control command/response encryption and signing inside TLS (#3339) * core: remote control command/response encryption inside TLS (except files, no signing) * sign/verify * update simplexmq * fix lazy * remove RSNone --- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat/Remote.hs | 22 ++-- src/Simplex/Chat/Remote/Protocol.hs | 188 +++++++++++++++++++++------- src/Simplex/Chat/Remote/Types.hs | 28 ++++- stack.yaml | 2 +- 6 files changed, 187 insertions(+), 57 deletions(-) diff --git a/cabal.project b/cabal.project index ea6129f3f6..8255bb7938 100644 --- a/cabal.project +++ b/cabal.project @@ -9,7 +9,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: bd06b47a9df13506ee77251868a5a1d1e7cadafe + tag: 6a2e6b040ec8566de2f4cf97bae6700a6a5cbeda source-repository-package type: git diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index d9033088a8..26d4dbbeae 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."bd06b47a9df13506ee77251868a5a1d1e7cadafe" = "1x6hy3awxf10l5ai82p3fhsrv1flc24gxsw9jl1b0cl7iypxhmsp"; + "https://github.com/simplex-chat/simplexmq.git"."6a2e6b040ec8566de2f4cf97bae6700a6a5cbeda" = "0diwdkwxxrly01ag7aygaa86ycwz13q2majvn48yg495zvqkrp7n"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/kazu-yamamoto/http2.git"."f5525b755ff2418e6e6ecc69e877363b0d0bcaeb" = "0fyx0047gvhm99ilp212mmz37j84cwrfnpmssib5dw363fyb88b6"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; diff --git a/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs index a5ddc3439e..53b63d72b1 100644 --- a/src/Simplex/Chat/Remote.hs +++ b/src/Simplex/Chat/Remote.hs @@ -60,7 +60,7 @@ import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) import qualified Simplex.Messaging.Crypto.File as CF import Simplex.Messaging.Encoding.String (StrEncoding (..)) import qualified Simplex.Messaging.TMap as TM -import Simplex.Messaging.Transport (TLS, closeConnection) +import Simplex.Messaging.Transport (TLS, closeConnection, tlsUniq) import Simplex.Messaging.Transport.Client (TransportHost (..)) import Simplex.Messaging.Transport.HTTP2.Client (HTTP2ClientError, closeHTTP2Client) import Simplex.Messaging.Transport.HTTP2.File (hSendFile) @@ -178,7 +178,7 @@ startRemoteHost' rh_ = do atomically $ writeTMVar currentKey rhKey' disconnected <- toIO $ onDisconnected remoteHostId httpClient <- liftEitherError (httpError rhKey') $ attachRevHTTP2Client disconnected tls - let rhClient = mkRemoteHostClient httpClient sessionKeys storePath hostInfo + rhClient <- mkRemoteHostClient httpClient sessionKeys sessId storePath hostInfo pollAction <- async $ pollEvents remoteHostId rhClient withRemoteHostSession rhKey' $ \case RHSessionConfirmed _ RHPendingSession {} -> Right ((), RHSessionConnected {tls, rhClient, pollAction, storePath}) @@ -358,8 +358,8 @@ connectRemoteCtrl inv@RCSignedInvitation {invitation = RCInvitation {ca, app}} = encryptFiles <- chatReadVar encryptLocalFiles pure HostAppInfo {appVersion, deviceName = hostDeviceName, encoding = localEncoding, encryptFiles} -handleRemoteCommand :: forall m. ChatMonad m => (ByteString -> m ChatResponse) -> CtrlSessKeys -> TBQueue ChatResponse -> HTTP2Request -> m () -handleRemoteCommand execChatCommand _sessionKeys remoteOutputQ HTTP2Request {request, reqBody, sendResponse} = do +handleRemoteCommand :: forall m. ChatMonad m => (ByteString -> m ChatResponse) -> RemoteCrypto -> TBQueue ChatResponse -> HTTP2Request -> m () +handleRemoteCommand execChatCommand encryption remoteOutputQ HTTP2Request {request, reqBody, sendResponse} = do logDebug "handleRemoteCommand" liftRC (tryRemoteError parseRequest) >>= \case Right (getNext, rc) -> do @@ -370,8 +370,8 @@ handleRemoteCommand execChatCommand _sessionKeys remoteOutputQ HTTP2Request {req where parseRequest :: ExceptT RemoteProtocolError IO (GetChunk, RemoteCommand) parseRequest = do - (header, getNext) <- parseHTTP2Body request reqBody - (getNext,) <$> liftEitherWith RPEInvalidJSON (J.eitherDecodeStrict' header) + (header, getNext) <- parseDecryptHTTP2Body encryption request reqBody + (getNext,) <$> liftEitherWith RPEInvalidJSON (J.eitherDecode header) replyError = reply . RRChatResponse . CRChatCmdError Nothing processCommand :: User -> GetChunk -> RemoteCommand -> m () processCommand user getNext = \case @@ -382,9 +382,10 @@ handleRemoteCommand execChatCommand _sessionKeys remoteOutputQ HTTP2Request {req reply :: RemoteResponse -> m () reply = (`replyWith` \_ -> pure ()) replyWith :: Respond m - replyWith rr attach = + replyWith rr attach = do + resp <- liftRC $ encryptEncodeHTTP2Body encryption $ J.encode rr liftIO . sendResponse . responseStreaming N.status200 [] $ \send flush -> do - send $ sizePrefixedEncode rr + send resp attach send flush @@ -482,11 +483,12 @@ verifyRemoteCtrlSession execChatCommand sessCode' = cleanupOnError $ do _ -> throwError $ ChatErrorRemoteCtrl RCEBadState let verified = sameVerificationCode sessCode' sessionCode liftIO $ confirmCtrlSession client verified - unless verified $ throwError $ ChatErrorRemoteCtrl RCEBadVerificationCode + unless verified $ throwError $ ChatErrorRemoteCtrl $ RCEProtocolError PRESessionCode (rcsSession@RCCtrlSession {tls, sessionKeys}, rcCtrlPairing) <- takeRCStep vars rc@RemoteCtrl {remoteCtrlId} <- upsertRemoteCtrl ctrlName rcCtrlPairing remoteOutputQ <- asks (tbqSize . config) >>= newTBQueueIO - http2Server <- async $ attachHTTP2Server tls $ handleRemoteCommand execChatCommand sessionKeys remoteOutputQ + encryption <- mkCtrlRemoteCrypto sessionKeys $ tlsUniq tls + http2Server <- async $ attachHTTP2Server tls $ handleRemoteCommand execChatCommand encryption remoteOutputQ void . forkIO $ monitor http2Server withRemoteCtrlSession $ \case RCSessionPendingConfirmation {} -> Right ((), RCSessionConnected {remoteCtrlId, rcsClient = client, rcsSession, tls, http2Server, remoteOutputQ}) diff --git a/src/Simplex/Chat/Remote/Protocol.hs b/src/Simplex/Chat/Remote/Protocol.hs index 62db487cbd..eae71d09c7 100644 --- a/src/Simplex/Chat/Remote/Protocol.hs +++ b/src/Simplex/Chat/Remote/Protocol.hs @@ -6,19 +6,26 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TupleSections #-} module Simplex.Chat.Remote.Protocol where import Control.Monad import Control.Monad.Except +import Control.Monad.Reader +import Crypto.Hash (SHA512) +import qualified Crypto.Hash as CH import Data.Aeson ((.=)) import qualified Data.Aeson as J import qualified Data.Aeson.Key as JK import qualified Data.Aeson.KeyMap as JM import Data.Aeson.TH (deriveJSON) import qualified Data.Aeson.Types as JT +import qualified Data.ByteArray as BA import Data.ByteString (ByteString) -import Data.ByteString.Builder (Builder, lazyByteString, word32BE) +import qualified Data.ByteString as B +import Data.ByteString.Builder (Builder, byteString, lazyByteString) import qualified Data.ByteString.Lazy as LB import Data.String (fromString) import Data.Text (Text) @@ -26,19 +33,25 @@ import Data.Text.Encoding (decodeUtf8) import Data.Word (Word32) import qualified Network.HTTP.Types as N import qualified Network.HTTP2.Client as H -import Network.Transport.Internal (decodeWord32) +import Network.Transport.Internal (decodeWord32, encodeWord32) import Simplex.Chat.Controller import Simplex.Chat.Remote.Transport import Simplex.Chat.Remote.Types import Simplex.FileTransfer.Description (FileDigest (..)) +import Simplex.Messaging.Agent.Client (agentDRG) +import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.File (CryptoFile (..)) +import Simplex.Messaging.Crypto.Lazy (LazyByteString) +import Simplex.Messaging.Encoding import Simplex.Messaging.Parsers (dropPrefix, taggedObjectJSON, pattern SingleFieldJSONTag, pattern TaggedObjectJSONData, pattern TaggedObjectJSONTag) import Simplex.Messaging.Transport.Buffer (getBuffered) import Simplex.Messaging.Transport.HTTP2 (HTTP2Body (..), HTTP2BodyChunk, getBodyChunk) import Simplex.Messaging.Transport.HTTP2.Client (HTTP2Client, HTTP2Response (..), closeHTTP2Client, sendRequestDirect) import Simplex.Messaging.Transport.HTTP2.File (hSendFile) -import Simplex.Messaging.Util (liftEitherError, liftEitherWith, tshow) -import Simplex.RemoteControl.Types (HostSessKeys) +import Simplex.Messaging.Util (liftEitherError, liftEitherWith, liftError, tshow) +import Simplex.RemoteControl.Types (CtrlSessKeys (..), HostSessKeys (..), RCErrorType (..), SessionCode) +import Simplex.RemoteControl.Client (xrcpBlockSize) +import qualified Simplex.RemoteControl.Client as RC import System.FilePath (takeFileName, ()) import UnliftIO @@ -64,16 +77,29 @@ $(deriveJSON (taggedObjectJSON $ dropPrefix "RR") ''RemoteResponse) -- * Client side / desktop -mkRemoteHostClient :: HTTP2Client -> HostSessKeys -> FilePath -> HostAppInfo -> RemoteHostClient -mkRemoteHostClient httpClient sessionKeys storePath HostAppInfo {encoding, deviceName, encryptFiles} = - RemoteHostClient - { hostEncoding = encoding, - hostDeviceName = deviceName, - httpClient, - encryptHostFiles = encryptFiles, - sessionKeys, - storePath - } +mkRemoteHostClient :: ChatMonad m => HTTP2Client -> HostSessKeys -> SessionCode -> FilePath -> HostAppInfo -> m RemoteHostClient +mkRemoteHostClient httpClient sessionKeys sessionCode storePath HostAppInfo {encoding, deviceName, encryptFiles} = do + drg <- asks $ agentDRG . smpAgent + counter <- newTVarIO 1 + let HostSessKeys {hybridKey, idPrivKey, sessPrivKey} = sessionKeys + signatures = RSSign {idPrivKey, sessPrivKey} + encryption = RemoteCrypto {drg, counter, sessionCode, hybridKey, signatures} + pure + RemoteHostClient + { hostEncoding = encoding, + hostDeviceName = deviceName, + httpClient, + encryption, + encryptHostFiles = encryptFiles, + storePath + } + +mkCtrlRemoteCrypto :: ChatMonad m => CtrlSessKeys -> SessionCode -> m RemoteCrypto +mkCtrlRemoteCrypto CtrlSessKeys {hybridKey, idPubKey, sessPubKey} sessionCode = do + drg <- asks $ agentDRG . smpAgent + counter <- newTVarIO 1 + let signatures = RSVerify {idPubKey, sessPubKey} + pure RemoteCrypto {drg, counter, sessionCode, hybridKey, signatures} closeRemoteHostClient :: MonadIO m => RemoteHostClient -> m () closeRemoteHostClient RemoteHostClient {httpClient} = liftIO $ closeHTTP2Client httpClient @@ -81,28 +107,28 @@ closeRemoteHostClient RemoteHostClient {httpClient} = liftIO $ closeHTTP2Client -- ** Commands remoteSend :: RemoteHostClient -> ByteString -> ExceptT RemoteProtocolError IO ChatResponse -remoteSend RemoteHostClient {httpClient, hostEncoding} cmd = - sendRemoteCommand' httpClient hostEncoding Nothing RCSend {command = decodeUtf8 cmd} >>= \case +remoteSend c cmd = + sendRemoteCommand' c Nothing RCSend {command = decodeUtf8 cmd} >>= \case RRChatResponse cr -> pure cr r -> badResponse r remoteRecv :: RemoteHostClient -> Int -> ExceptT RemoteProtocolError IO (Maybe ChatResponse) -remoteRecv RemoteHostClient {httpClient, hostEncoding} ms = - sendRemoteCommand' httpClient hostEncoding Nothing RCRecv {wait = ms} >>= \case +remoteRecv c ms = + sendRemoteCommand' c Nothing RCRecv {wait = ms} >>= \case RRChatEvent cr_ -> pure cr_ r -> badResponse r remoteStoreFile :: RemoteHostClient -> FilePath -> FilePath -> ExceptT RemoteProtocolError IO FilePath -remoteStoreFile RemoteHostClient {httpClient, hostEncoding} localPath fileName = do +remoteStoreFile c localPath fileName = do (fileSize, fileDigest) <- getFileInfo localPath - let send h = sendRemoteCommand' httpClient hostEncoding (Just (h, fileSize)) RCStoreFile {fileName, fileSize, fileDigest} + let send h = sendRemoteCommand' c (Just (h, fileSize)) RCStoreFile {fileName, fileSize, fileDigest} withFile localPath ReadMode send >>= \case RRFileStored {filePath = filePath'} -> pure filePath' r -> badResponse r remoteGetFile :: RemoteHostClient -> FilePath -> RemoteFile -> ExceptT RemoteProtocolError IO () -remoteGetFile RemoteHostClient {httpClient, hostEncoding} destDir rf@RemoteFile {fileSource = CryptoFile {filePath}} = - sendRemoteCommand httpClient hostEncoding Nothing RCGetFile {file = rf} >>= \case +remoteGetFile c destDir rf@RemoteFile {fileSource = CryptoFile {filePath}} = + sendRemoteCommand c Nothing RCGetFile {file = rf} >>= \case (getChunk, RRFile {fileSize, fileDigest}) -> do -- TODO we could optimize by checking size and hash before receiving the file let localPath = destDir takeFileName filePath @@ -110,18 +136,19 @@ remoteGetFile RemoteHostClient {httpClient, hostEncoding} destDir rf@RemoteFile (_, r) -> badResponse r -- TODO validate there is no attachment -sendRemoteCommand' :: HTTP2Client -> PlatformEncoding -> Maybe (Handle, Word32) -> RemoteCommand -> ExceptT RemoteProtocolError IO RemoteResponse -sendRemoteCommand' http remoteEncoding attachment_ rc = snd <$> sendRemoteCommand http remoteEncoding attachment_ rc +sendRemoteCommand' :: RemoteHostClient -> Maybe (Handle, Word32) -> RemoteCommand -> ExceptT RemoteProtocolError IO RemoteResponse +sendRemoteCommand' c attachment_ rc = snd <$> sendRemoteCommand c attachment_ rc -sendRemoteCommand :: HTTP2Client -> PlatformEncoding -> Maybe (Handle, Word32) -> RemoteCommand -> ExceptT RemoteProtocolError IO (Int -> IO ByteString, RemoteResponse) -sendRemoteCommand http remoteEncoding attachment_ rc = do - HTTP2Response {response, respBody} <- liftEitherError (RPEHTTP2 . tshow) $ sendRequestDirect http httpRequest Nothing - (header, getNext) <- parseHTTP2Body response respBody - rr <- liftEitherWith (RPEInvalidJSON . fromString) $ J.eitherDecodeStrict header >>= JT.parseEither J.parseJSON . convertJSON remoteEncoding localEncoding +sendRemoteCommand :: RemoteHostClient -> Maybe (Handle, Word32) -> RemoteCommand -> ExceptT RemoteProtocolError IO (Int -> IO ByteString, RemoteResponse) +sendRemoteCommand RemoteHostClient {httpClient, hostEncoding, encryption} attachment_ cmd = do + req <- httpRequest <$> encryptEncodeHTTP2Body encryption (J.encode cmd) + HTTP2Response {response, respBody} <- liftEitherError (RPEHTTP2 . tshow) $ sendRequestDirect httpClient req Nothing + (header, getNext) <- parseDecryptHTTP2Body encryption response respBody + rr <- liftEitherWith (RPEInvalidJSON . fromString) $ J.eitherDecode header >>= JT.parseEither J.parseJSON . convertJSON hostEncoding localEncoding pure (getNext, rr) where - httpRequest = H.requestStreaming N.methodPost "/" mempty $ \send flush -> do - send $ sizePrefixedEncode rc + httpRequest cmdBld = H.requestStreaming N.methodPost "/" mempty $ \send flush -> do + send cmdBld case attachment_ of Nothing -> pure () Just (h, sz) -> hSendFile h send sz @@ -175,18 +202,93 @@ owsf2tagged = fst . convert pattern OwsfTag :: (JK.Key, J.Value) pattern OwsfTag = (SingleFieldJSONTag, J.Bool True) --- | Convert a command or a response into 'Builder'. -sizePrefixedEncode :: J.ToJSON a => a -> Builder -sizePrefixedEncode value = word32BE (fromIntegral $ LB.length json) <> lazyByteString json - where - json = J.encode value +-- ``` +-- commandBody = encBody sessSignature idSignature (attachment / noAttachment) +-- responseBody = encBody attachment; should match counter in the command +-- encBody = nonce encLength32 encrypted(tlsunique counter body) +-- attachment = %x01 nonce encLength32 encrypted(attachment) +-- noAttachment = %x00 +-- tlsunique = length 1*OCTET +-- nonce = 24*24 OCTET +-- counter = 8*8 OCTET ; int64 +-- encLength32 = 4*4 OCTET ; uint32, includes authTag +-- ``` --- | Parse HTTP request or response to a size-prefixed chunk and a function to read more. -parseHTTP2Body :: HTTP2BodyChunk a => a -> HTTP2Body -> ExceptT RemoteProtocolError IO (ByteString, Int -> IO ByteString) -parseHTTP2Body hr HTTP2Body {bodyBuffer} = do - rSize <- liftIO $ decodeWord32 <$> getNext 4 - when (rSize > fromIntegral (maxBound :: Int)) $ throwError RPEInvalidSize - r <- liftIO $ getNext $ fromIntegral rSize - pure (r, getNext) +-- See https://github.com/simplex-chat/simplexmq/blob/master/rfcs/2023-10-25-remote-control.md for encoding + +encryptEncodeHTTP2Body :: RemoteCrypto -> LazyByteString -> ExceptT RemoteProtocolError IO Builder +encryptEncodeHTTP2Body RemoteCrypto {drg, counter, sessionCode, hybridKey, signatures} s = do + corrId <- atomically $ stateTVar counter $ \c -> (c, c + 1) + let pfx = smpEncode (sessionCode, corrId) + (nonce, ct) <- liftError PRERemoteControl $ RC.rcEncryptBody drg hybridKey $ LB.fromStrict pfx <> s + let ctLen = encodeWord32 (fromIntegral $ LB.length ct) + signed = LB.fromStrict (smpEncode nonce <> ctLen) <> ct + sigs <- bodySignatures signed + pure $ lazyByteString signed <> sigs where + bodySignatures :: LazyByteString -> ExceptT RemoteProtocolError IO Builder + bodySignatures signed = case signatures of + RSSign {idPrivKey, sessPrivKey} -> do + let hc = CH.hashUpdates (CH.hashInit @SHA512) (LB.toChunks signed) + ssig = sign sessPrivKey hc + idsig = sign idPrivKey $ CH.hashUpdate hc ssig + pure $ byteString $ smpEncode (ssig, idsig) + _ -> pure mempty + sign :: C.PrivateKeyEd25519 -> CH.Context SHA512 -> ByteString + sign k = C.signatureBytes . C.sign' k . BA.convert . CH.hashFinalize + +-- | Parse and decrypt HTTP2 request/response +parseDecryptHTTP2Body :: HTTP2BodyChunk a => RemoteCrypto -> a -> HTTP2Body -> ExceptT RemoteProtocolError IO (LazyByteString, Int -> IO ByteString) +parseDecryptHTTP2Body RemoteCrypto {hybridKey, sessionCode, signatures} hr HTTP2Body {bodyBuffer} = do + (nonce, ct) <- getBody + s <- liftError PRERemoteControl $ RC.rcDecryptBody hybridKey nonce ct + (,getNext) <$> parseBody s + where + getBody :: ExceptT RemoteProtocolError IO (C.CbNonce, LazyByteString) + getBody = do + nonceStr <- liftIO $ getNext 24 + nonce <- liftEitherWith RPEInvalidBody $ smpDecode nonceStr + ctLenStr <- liftIO $ getNext 4 + let ctLen = decodeWord32 ctLenStr + when (ctLen > fromIntegral (maxBound :: Int)) $ throwError RPEInvalidSize + chunks <- liftIO $ getLazy $ fromIntegral ctLen + let hc = CH.hashUpdates (CH.hashInit @SHA512) [nonceStr, ctLenStr] + hc' = CH.hashUpdates hc chunks + verifySignatures hc' + pure (nonce, LB.fromChunks chunks) + getLazy :: Int -> IO [ByteString] + getLazy 0 = pure [] + getLazy n = do + let sz = min n xrcpBlockSize + bs <- getNext sz + let n' = if B.length bs < sz then 0 else max 0 (n - xrcpBlockSize) + (bs :) <$> getLazy n' + verifySignatures :: CH.Context SHA512 -> ExceptT RemoteProtocolError IO () + verifySignatures hc = case signatures of + RSVerify {sessPubKey, idPubKey} -> do + ssig <- getSig + idsig <- getSig + verifySig sessPubKey ssig hc + verifySig idPubKey idsig $ CH.hashUpdate hc $ C.signatureBytes ssig + _ -> pure () + where + getSig = do + len <- liftIO $ B.head <$> getNext 1 + liftEitherError RPEInvalidBody $ C.decodeSignature <$> getNext (fromIntegral len) + verifySig key sig hc' = do + let signed = BA.convert $ CH.hashFinalize hc' + unless (C.verify' key sig signed) $ throwError $ PRERemoteControl RCECtrlAuth + parseBody :: LazyByteString -> ExceptT RemoteProtocolError IO LazyByteString + parseBody s = case LB.uncons s of + Nothing -> throwError $ RPEInvalidBody "empty body" + Just (scLen, rest) -> do + (sessCode', rest') <- takeBytes (fromIntegral scLen) rest + unless (sessCode' == sessionCode) $ throwError PRESessionCode + (_corrId, s') <- takeBytes 8 rest' + pure s' + where + takeBytes n s' = do + let (bs, rest) = LB.splitAt n s' + unless (LB.length bs == n) $ throwError PRESessionCode + pure (LB.toStrict bs, rest) getNext sz = getBuffered bodyBuffer sz Nothing $ getBodyChunk hr diff --git a/src/Simplex/Chat/Remote/Types.hs b/src/Simplex/Chat/Remote/Types.hs index 638707563f..3177ae3ef6 100644 --- a/src/Simplex/Chat/Remote/Types.hs +++ b/src/Simplex/Chat/Remote/Types.hs @@ -10,11 +10,16 @@ module Simplex.Chat.Remote.Types where import Control.Concurrent.Async (Async) +import Control.Concurrent.STM (TVar) import Control.Exception (Exception) +import Crypto.Random (ChaChaDRG) import qualified Data.Aeson.TH as J +import Data.ByteString (ByteString) import Data.Int (Int64) import Data.Text (Text) import Simplex.Chat.Remote.AppVersion +import qualified Simplex.Messaging.Crypto as C +import Simplex.Messaging.Crypto.SNTRUP761 (KEMHybridSecret) import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, sumTypeJSON) import Simplex.Messaging.Transport.HTTP2.Client (HTTP2Client) import Simplex.RemoteControl.Client @@ -26,11 +31,29 @@ data RemoteHostClient = RemoteHostClient { hostEncoding :: PlatformEncoding, hostDeviceName :: Text, httpClient :: HTTP2Client, - sessionKeys :: HostSessKeys, + encryption :: RemoteCrypto, encryptHostFiles :: Bool, storePath :: FilePath } +data RemoteCrypto = RemoteCrypto + { drg :: TVar ChaChaDRG, + counter :: TVar Int64, + sessionCode :: ByteString, + hybridKey :: KEMHybridSecret, + signatures :: RemoteSignatures + } + +data RemoteSignatures + = RSSign + { idPrivKey :: C.PrivateKeyEd25519, + sessPrivKey :: C.PrivateKeyEd25519 + } + | RSVerify + { idPubKey :: C.PublicKeyEd25519, + sessPubKey :: C.PublicKeyEd25519 + } + data RHPendingSession = RHPendingSession { rhKey :: RHKey, rchClient :: RCHostClient, @@ -49,6 +72,8 @@ data RemoteProtocolError RPEInvalidSize | -- | failed to parse RemoteCommand or RemoteResponse RPEInvalidJSON {invalidJSON :: String} + | RPEInvalidBody {invalidBody :: String} + | PRESessionCode | RPEIncompatibleEncoding | RPEUnexpectedFile | RPENoFile @@ -58,6 +83,7 @@ data RemoteProtocolError RPEUnexpectedResponse {response :: Text} | -- | A file already exists in the destination position RPEStoredFileExists + | PRERemoteControl {rcError :: RCErrorType} | RPEHTTP2 {http2Error :: Text} | RPEException {someException :: Text} deriving (Show, Exception) diff --git a/stack.yaml b/stack.yaml index c90140963a..11920f5317 100644 --- a/stack.yaml +++ b/stack.yaml @@ -49,7 +49,7 @@ extra-deps: # - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561 # - ../simplexmq - github: simplex-chat/simplexmq - commit: bd06b47a9df13506ee77251868a5a1d1e7cadafe + commit: 6a2e6b040ec8566de2f4cf97bae6700a6a5cbeda - github: kazu-yamamoto/http2 commit: f5525b755ff2418e6e6ecc69e877363b0d0bcaeb # - ../direct-sqlcipher