core: update remote host session state, terminate TLS in one more case (#3364)

* core: update remote host session state, terminate TLS in one more case

* name
This commit is contained in:
Evgeny Poberezkin 2023-11-13 20:16:34 +00:00 committed by GitHub
parent 598b6659cc
commit c91625b32a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 69 additions and 34 deletions

View File

@ -9,7 +9,7 @@ constraints: zip +disable-bzip2 +disable-zstd
source-repository-package source-repository-package
type: git type: git
location: https://github.com/simplex-chat/simplexmq.git location: https://github.com/simplex-chat/simplexmq.git
tag: 4f5d52ada47a15532766b2ff3d3781be629648d8 tag: e0b7942e45e36d92625e07c0c1ce9ca2375a0980
source-repository-package source-repository-package
type: git type: git

View File

@ -1,5 +1,5 @@
{ {
"https://github.com/simplex-chat/simplexmq.git"."c051ebab74632e0eb60686329ab3fad521736f79" = "1j7z3v3vk02nq4sw46flky1l4pjxfiypbwh5s77m6f81rc0vsjvi"; "https://github.com/simplex-chat/simplexmq.git"."e0b7942e45e36d92625e07c0c1ce9ca2375a0980" = "0swbcrmdirwqrk0kx5jmc5lcrzasccfwn3papb5c1p8hn0hjnzj7";
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
"https://github.com/kazu-yamamoto/http2.git"."f5525b755ff2418e6e6ecc69e877363b0d0bcaeb" = "0fyx0047gvhm99ilp212mmz37j84cwrfnpmssib5dw363fyb88b6"; "https://github.com/kazu-yamamoto/http2.git"."f5525b755ff2418e6e6ecc69e877363b0d0bcaeb" = "0fyx0047gvhm99ilp212mmz37j84cwrfnpmssib5dw363fyb88b6";
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";

View File

@ -32,7 +32,7 @@ import qualified Data.Map.Strict as M
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as T import qualified Data.Text as T
import Data.Text.Encoding (encodeUtf8) import Data.Text.Encoding (decodeLatin1, encodeUtf8)
import Data.Word (Word16, Word32) import Data.Word (Word16, Word32)
import qualified Network.HTTP.Types as N import qualified Network.HTTP.Types as N
import Network.HTTP2.Server (responseStreaming) import Network.HTTP2.Server (responseStreaming)
@ -129,7 +129,7 @@ startRemoteHost rh_ = do
(rhKey, multicast, remoteHost_, pairing) <- case rh_ of (rhKey, multicast, remoteHost_, pairing) <- case rh_ of
Just (rhId, multicast) -> do Just (rhId, multicast) -> do
rh@RemoteHost {hostPairing} <- withStore $ \db -> getRemoteHost db rhId rh@RemoteHost {hostPairing} <- withStore $ \db -> getRemoteHost db rhId
pure (RHId rhId, multicast, Just $ remoteHostInfo rh True, hostPairing) -- get from the database, start multicast if requested pure (RHId rhId, multicast, Just $ remoteHostInfo rh $ Just RHSStarting, hostPairing) -- get from the database, start multicast if requested
Nothing -> (RHNew,False,Nothing,) <$> rcNewHostPairing Nothing -> (RHNew,False,Nothing,) <$> rcNewHostPairing
withRemoteHostSession_ rhKey $ maybe (Right ((), Just RHSessionStarting)) (\_ -> Left $ ChatErrorRemoteHost rhKey RHEBusy) withRemoteHostSession_ rhKey $ maybe (Right ((), Just RHSessionStarting)) (\_ -> Left $ ChatErrorRemoteHost rhKey RHEBusy)
ctrlAppInfo <- mkCtrlAppInfo ctrlAppInfo <- mkCtrlAppInfo
@ -141,7 +141,9 @@ startRemoteHost rh_ = do
handleHostError rhKeyVar $ waitForHostSession remoteHost_ rhKey rhKeyVar vars handleHostError rhKeyVar $ waitForHostSession remoteHost_ rhKey rhKeyVar vars
let rhs = RHPendingSession {rhKey, rchClient, rhsWaitSession, remoteHost_} let rhs = RHPendingSession {rhKey, rchClient, rhsWaitSession, remoteHost_}
withRemoteHostSession rhKey $ \case withRemoteHostSession rhKey $ \case
RHSessionStarting -> Right ((), RHSessionConnecting rhs) RHSessionStarting ->
let inv = decodeLatin1 $ strEncode invitation
in Right ((), RHSessionConnecting inv rhs)
_ -> Left $ ChatErrorRemoteHost rhKey RHEBadState _ -> Left $ ChatErrorRemoteHost rhKey RHEBadState
(remoteHost_, invitation) <$ atomically (putTMVar cmdOk ()) (remoteHost_, invitation) <$ atomically (putTMVar cmdOk ())
where where
@ -162,18 +164,22 @@ startRemoteHost rh_ = do
sessions <- asks remoteHostSessions sessions <- asks remoteHostSessions
session_ <- atomically $ readTVar rhKeyVar >>= (`TM.lookupDelete` sessions) session_ <- atomically $ readTVar rhKeyVar >>= (`TM.lookupDelete` sessions)
mapM_ (liftIO . cancelRemoteHost) session_ mapM_ (liftIO . cancelRemoteHost) session_
waitForHostSession :: ChatMonad m => Maybe RemoteHostInfo -> RHKey -> TVar RHKey -> RCStepTMVar (ByteString, RCStepTMVar (RCHostSession, RCHostHello, RCHostPairing)) -> m () waitForHostSession :: ChatMonad m => Maybe RemoteHostInfo -> RHKey -> TVar RHKey -> RCStepTMVar (ByteString, TLS, RCStepTMVar (RCHostSession, RCHostHello, RCHostPairing)) -> m ()
waitForHostSession remoteHost_ rhKey rhKeyVar vars = do waitForHostSession remoteHost_ rhKey rhKeyVar vars = do
(sessId, vars') <- takeRCStep vars -- no timeout, waiting for user to scan invite (sessId, tls, vars') <- takeRCStep vars -- no timeout, waiting for user to scan invite
let sessCode = verificationCode sessId
withRemoteHostSession rhKey $ \case
RHSessionConnecting _inv rhs' -> Right ((), RHSessionPendingConfirmation sessCode tls rhs') -- TODO check it's the same session?
_ -> Left $ ChatErrorRemoteHost rhKey RHEBadState
toView $ CRRemoteHostSessionCode {remoteHost_, sessionCode = verificationCode sessId} -- display confirmation code, wait for mobile to confirm toView $ CRRemoteHostSessionCode {remoteHost_, sessionCode = verificationCode sessId} -- display confirmation code, wait for mobile to confirm
(RCHostSession {tls, sessionKeys}, rhHello, pairing') <- takeRCStep vars' -- no timeout, waiting for user to compare the code (RCHostSession {sessionKeys}, rhHello, pairing') <- takeRCStep vars' -- no timeout, waiting for user to compare the code
hostInfo@HostAppInfo {deviceName = hostDeviceName} <- hostInfo@HostAppInfo {deviceName = hostDeviceName} <-
liftError (ChatErrorRemoteHost rhKey) $ parseHostAppInfo rhHello liftError (ChatErrorRemoteHost rhKey) $ parseHostAppInfo rhHello
withRemoteHostSession rhKey $ \case withRemoteHostSession rhKey $ \case
RHSessionConnecting rhs' -> Right ((), RHSessionConfirmed tls rhs') -- TODO check it's the same session? RHSessionPendingConfirmation _ tls' rhs' -> Right ((), RHSessionConfirmed tls' rhs') -- TODO check it's the same session?
_ -> Left $ ChatErrorRemoteHost rhKey RHEBadState _ -> Left $ ChatErrorRemoteHost rhKey RHEBadState
-- update remoteHost with updated pairing -- update remoteHost with updated pairing
rhi@RemoteHostInfo {remoteHostId, storePath} <- upsertRemoteHost pairing' remoteHost_ hostDeviceName rhi@RemoteHostInfo {remoteHostId, storePath} <- upsertRemoteHost pairing' remoteHost_ hostDeviceName RHSConfirmed
let rhKey' = RHId remoteHostId -- rhKey may be invalid after upserting on RHNew let rhKey' = RHId remoteHostId -- rhKey may be invalid after upserting on RHNew
when (rhKey' /= rhKey) $ do when (rhKey' /= rhKey) $ do
atomically $ writeTVar rhKeyVar rhKey' atomically $ writeTVar rhKeyVar rhKey'
@ -187,18 +193,18 @@ startRemoteHost rh_ = do
_ -> Left $ ChatErrorRemoteHost rhKey' RHEBadState _ -> Left $ ChatErrorRemoteHost rhKey' RHEBadState
chatWriteVar currentRemoteHost $ Just remoteHostId -- this is required for commands to be passed to remote host chatWriteVar currentRemoteHost $ Just remoteHostId -- this is required for commands to be passed to remote host
toView $ CRRemoteHostConnected rhi toView $ CRRemoteHostConnected rhi
upsertRemoteHost :: ChatMonad m => RCHostPairing -> Maybe RemoteHostInfo -> Text -> m RemoteHostInfo upsertRemoteHost :: ChatMonad m => RCHostPairing -> Maybe RemoteHostInfo -> Text -> RemoteHostSessionState -> m RemoteHostInfo
upsertRemoteHost pairing'@RCHostPairing {knownHost = kh_} rhi_ hostDeviceName = do upsertRemoteHost pairing'@RCHostPairing {knownHost = kh_} rhi_ hostDeviceName state = do
KnownHostPairing {hostDhPubKey = hostDhPubKey'} <- maybe (throwError . ChatError $ CEInternalError "KnownHost is known after verification") pure kh_ KnownHostPairing {hostDhPubKey = hostDhPubKey'} <- maybe (throwError . ChatError $ CEInternalError "KnownHost is known after verification") pure kh_
case rhi_ of case rhi_ of
Nothing -> do Nothing -> do
storePath <- liftIO randomStorePath storePath <- liftIO randomStorePath
rh@RemoteHost {remoteHostId} <- withStore $ \db -> insertRemoteHost db hostDeviceName storePath pairing' >>= getRemoteHost db rh@RemoteHost {remoteHostId} <- withStore $ \db -> insertRemoteHost db hostDeviceName storePath pairing' >>= getRemoteHost db
setNewRemoteHostId RHNew remoteHostId setNewRemoteHostId RHNew remoteHostId
pure $ remoteHostInfo rh True pure $ remoteHostInfo rh $ Just state
Just rhi@RemoteHostInfo {remoteHostId} -> do Just rhi@RemoteHostInfo {remoteHostId} -> do
withStore' $ \db -> updateHostPairing db remoteHostId hostDeviceName hostDhPubKey' withStore' $ \db -> updateHostPairing db remoteHostId hostDeviceName hostDhPubKey'
pure (rhi :: RemoteHostInfo) {sessionActive = True} pure (rhi :: RemoteHostInfo) {sessionState = Just state}
onDisconnected :: ChatMonad m => RemoteHostId -> m () onDisconnected :: ChatMonad m => RemoteHostId -> m ()
onDisconnected remoteHostId = do onDisconnected remoteHostId = do
logDebug "HTTP2 client disconnected" logDebug "HTTP2 client disconnected"
@ -225,7 +231,10 @@ closeRemoteHost rhKey = do
cancelRemoteHost :: RemoteHostSession -> IO () cancelRemoteHost :: RemoteHostSession -> IO ()
cancelRemoteHost = \case cancelRemoteHost = \case
RHSessionStarting -> pure () RHSessionStarting -> pure ()
RHSessionConnecting rhs -> cancelPendingSession rhs RHSessionConnecting _inv rhs -> cancelPendingSession rhs
RHSessionPendingConfirmation _sessCode tls rhs -> do
cancelPendingSession rhs
closeConnection tls
RHSessionConfirmed tls rhs -> do RHSessionConfirmed tls rhs -> do
cancelPendingSession rhs cancelPendingSession rhs
closeConnection tls closeConnection tls
@ -245,26 +254,26 @@ randomStorePath = B.unpack . B64U.encode <$> getRandomBytes 12
listRemoteHosts :: ChatMonad m => m [RemoteHostInfo] listRemoteHosts :: ChatMonad m => m [RemoteHostInfo]
listRemoteHosts = do listRemoteHosts = do
active <- chatReadVar remoteHostSessions sessions <- chatReadVar remoteHostSessions
map (rhInfo active) <$> withStore' getRemoteHosts map (rhInfo sessions) <$> withStore' getRemoteHosts
where where
rhInfo active rh@RemoteHost {remoteHostId} = rhInfo sessions rh@RemoteHost {remoteHostId} =
remoteHostInfo rh (M.member (RHId remoteHostId) active) remoteHostInfo rh (rhsSessionState <$> M.lookup (RHId remoteHostId) sessions)
switchRemoteHost :: ChatMonad m => Maybe RemoteHostId -> m (Maybe RemoteHostInfo) switchRemoteHost :: ChatMonad m => Maybe RemoteHostId -> m (Maybe RemoteHostInfo)
switchRemoteHost rhId_ = do switchRemoteHost rhId_ = do
rhi_ <- forM rhId_ $ \rhId -> do rhi_ <- forM rhId_ $ \rhId -> do
let rhKey = RHId rhId let rhKey = RHId rhId
rhi <- (`remoteHostInfo` True) <$> withStore (`getRemoteHost` rhId) rh <- withStore (`getRemoteHost` rhId)
active <- chatReadVar remoteHostSessions sessions <- chatReadVar remoteHostSessions
case M.lookup rhKey active of case M.lookup rhKey sessions of
Just RHSessionConnected {} -> pure rhi Just RHSessionConnected {} -> pure $ remoteHostInfo rh $ Just RHSConnected
_ -> throwError $ ChatErrorRemoteHost rhKey RHEInactive _ -> throwError $ ChatErrorRemoteHost rhKey RHEInactive
rhi_ <$ chatWriteVar currentRemoteHost rhId_ rhi_ <$ chatWriteVar currentRemoteHost rhId_
remoteHostInfo :: RemoteHost -> Bool -> RemoteHostInfo remoteHostInfo :: RemoteHost -> Maybe RemoteHostSessionState -> RemoteHostInfo
remoteHostInfo RemoteHost {remoteHostId, storePath, hostDeviceName} sessionActive = remoteHostInfo RemoteHost {remoteHostId, storePath, hostDeviceName} sessionState =
RemoteHostInfo {remoteHostId, storePath, hostDeviceName, sessionActive} RemoteHostInfo {remoteHostId, storePath, hostDeviceName, sessionState}
deleteRemoteHost :: ChatMonad m => RemoteHostId -> m () deleteRemoteHost :: ChatMonad m => RemoteHostId -> m ()
deleteRemoteHost rhId = do deleteRemoteHost rhId = do

View File

@ -3,6 +3,7 @@
{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE GADTs #-} {-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
@ -63,7 +64,8 @@ data RHPendingSession = RHPendingSession
data RemoteHostSession data RemoteHostSession
= RHSessionStarting = RHSessionStarting
| RHSessionConnecting {rhPendingSession :: RHPendingSession} | RHSessionConnecting {invitation :: Text, rhPendingSession :: RHPendingSession}
| RHSessionPendingConfirmation {sessionCode :: Text, tls :: TLS, rhPendingSession :: RHPendingSession}
| RHSessionConfirmed {tls :: TLS, rhPendingSession :: RHPendingSession} | RHSessionConfirmed {tls :: TLS, rhPendingSession :: RHPendingSession}
| RHSessionConnected | RHSessionConnected
{ rchClient :: RCHostClient, { rchClient :: RCHostClient,
@ -73,6 +75,22 @@ data RemoteHostSession
storePath :: FilePath storePath :: FilePath
} }
data RemoteHostSessionState
= RHSStarting
| RHSConnecting {invitation :: Text}
| RHSPendingConfirmation {sessionCode :: Text}
| RHSConfirmed
| RHSConnected
deriving (Show)
rhsSessionState :: RemoteHostSession -> RemoteHostSessionState
rhsSessionState = \case
RHSessionStarting -> RHSStarting
RHSessionConnecting {invitation} -> RHSConnecting {invitation}
RHSessionPendingConfirmation {sessionCode} -> RHSPendingConfirmation {sessionCode}
RHSessionConfirmed {} -> RHSConfirmed
RHSessionConnected {} -> RHSConnected
data RemoteProtocolError data RemoteProtocolError
= -- | size prefix is malformed = -- | size prefix is malformed
RPEInvalidSize RPEInvalidSize
@ -112,7 +130,7 @@ data RemoteHostInfo = RemoteHostInfo
{ remoteHostId :: RemoteHostId, { remoteHostId :: RemoteHostId,
hostDeviceName :: Text, hostDeviceName :: Text,
storePath :: FilePath, storePath :: FilePath,
sessionActive :: Bool sessionState :: Maybe RemoteHostSessionState
} }
deriving (Show) deriving (Show)
@ -174,6 +192,8 @@ $(J.deriveJSON (sumTypeJSON $ dropPrefix "RH") ''RHKey)
$(J.deriveJSON (enumJSON $ dropPrefix "PE") ''PlatformEncoding) $(J.deriveJSON (enumJSON $ dropPrefix "PE") ''PlatformEncoding)
$(J.deriveJSON (sumTypeJSON $ dropPrefix "RHS") ''RemoteHostSessionState)
$(J.deriveJSON defaultJSON ''RemoteHostInfo) $(J.deriveJSON defaultJSON ''RemoteHostInfo)
$(J.deriveJSON defaultJSON ''RemoteCtrlInfo) $(J.deriveJSON defaultJSON ''RemoteCtrlInfo)

View File

@ -1704,8 +1704,14 @@ viewRemoteHosts = \case
[] -> ["No remote hosts"] [] -> ["No remote hosts"]
hs -> "Remote hosts: " : map viewRemoteHostInfo hs hs -> "Remote hosts: " : map viewRemoteHostInfo hs
where where
viewRemoteHostInfo RemoteHostInfo {remoteHostId, hostDeviceName, sessionActive} = viewRemoteHostInfo RemoteHostInfo {remoteHostId, hostDeviceName, sessionState} =
plain $ tshow remoteHostId <> ". " <> hostDeviceName <> if sessionActive then " (active)" else "" plain $ tshow remoteHostId <> ". " <> hostDeviceName <> maybe "" viewSessionState sessionState
viewSessionState = \case
RHSStarting -> " (starting)"
RHSConnecting _ -> " (connecting)"
RHSPendingConfirmation {sessionCode} -> " (pending confirmation, code: " <> sessionCode <> ")"
RHSConfirmed -> " (confirmed)"
RHSConnected -> " (connected)"
viewRemoteCtrls :: [RemoteCtrlInfo] -> [StyledString] viewRemoteCtrls :: [RemoteCtrlInfo] -> [StyledString]
viewRemoteCtrls = \case viewRemoteCtrls = \case
@ -1713,7 +1719,7 @@ viewRemoteCtrls = \case
hs -> "Remote controllers: " : map viewRemoteCtrlInfo hs hs -> "Remote controllers: " : map viewRemoteCtrlInfo hs
where where
viewRemoteCtrlInfo RemoteCtrlInfo {remoteCtrlId, ctrlDeviceName, sessionActive} = viewRemoteCtrlInfo RemoteCtrlInfo {remoteCtrlId, ctrlDeviceName, sessionActive} =
plain $ tshow remoteCtrlId <> ". " <> ctrlDeviceName <> if sessionActive then " (active)" else "" plain $ tshow remoteCtrlId <> ". " <> ctrlDeviceName <> if sessionActive then " (connected)" else ""
-- TODO fingerprint, accepted? -- TODO fingerprint, accepted?
viewRemoteCtrl :: RemoteCtrlInfo -> StyledString viewRemoteCtrl :: RemoteCtrlInfo -> StyledString

View File

@ -49,7 +49,7 @@ extra-deps:
# - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561 # - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561
# - ../simplexmq # - ../simplexmq
- github: simplex-chat/simplexmq - github: simplex-chat/simplexmq
commit: c051ebab74632e0eb60686329ab3fad521736f79 commit: e0b7942e45e36d92625e07c0c1ce9ca2375a0980
- github: kazu-yamamoto/http2 - github: kazu-yamamoto/http2
commit: f5525b755ff2418e6e6ecc69e877363b0d0bcaeb commit: f5525b755ff2418e6e6ecc69e877363b0d0bcaeb
# - ../direct-sqlcipher # - ../direct-sqlcipher

View File

@ -57,11 +57,11 @@ remoteHandshakeTest viaDesktop = testChat2 aliceProfile aliceDesktopProfile $ \m
desktop ##> "/list remote hosts" desktop ##> "/list remote hosts"
desktop <## "Remote hosts:" desktop <## "Remote hosts:"
desktop <## "1. Mobile (active)" desktop <## "1. Mobile (connected)"
mobile ##> "/list remote ctrls" mobile ##> "/list remote ctrls"
mobile <## "Remote controllers:" mobile <## "Remote controllers:"
mobile <## "1. My desktop (active)" mobile <## "1. My desktop (connected)"
if viaDesktop then stopDesktop mobile desktop else stopMobile mobile desktop if viaDesktop then stopDesktop mobile desktop else stopMobile mobile desktop