SMP proxy: protocol (#954)

* WIP: proxy-related types

* test plan

* buildable with stubs

* add auth test

* update protocol

* fix

* update rfc

* update protocol/types

* disable test

---------

Co-authored-by: Evgeny Poberezkin <[email protected]>
This commit is contained in:
Alexander Bondarenko
2024-04-12 18:04:29 +01:00
committed by GitHub
co-authored by Evgeny Poberezkin
parent 036b7523a5
commit 875ddd80d6
13 changed files with 295 additions and 56 deletions
+23
View File
@@ -0,0 +1,23 @@
common:
corrId - random BS, used as CbNonce
entityId - p2r tlsUniq
# setup
s->p: "proxy", uri, auth?
# unless connected
p->r: "p_handshake"
p<-r: "r_key", tls-signed dh pub
s<-r: "r_key", tls-signed dh pub # reply entityId contains tlsUniq
# working
s ; generate random dh priv, make shared secret
s->p: s2r("forward", random dh pub, SEND command blob)
p->r: p2r("forward", random dh pub, s2r("forward", ...)))
r->c@ "msg", ...
p<-r: p2r("r_res", s2r("ok" / "error", error))
s<-p@ s2r("ok" / "error", error)
# expired
p<-r@ p2r("error", "key expired")
s<-p@ "error", "key expired"
s ; reconnect
+46 -41
View File
@@ -2,9 +2,9 @@
## Problem
SMP protocol relays are chosen and can be controlled by the message recipients. It means that the recipients can find out IP addresses of message senders by modifying SMP relay code (or by using proxies and timing correlation), unless the senders use VPN or some overlay network. Tor is an audequate solution in most cases to mitigate it, but it requires additional technical knowledge to install and configure (even installing Orbot on Android is seen as "complex" by many users), and reduces usability because of higher latency.
SMP protocol relays are chosen and can be controlled by the message recipients. It means that the recipients can find out IP addresses of message senders by modifying SMP relay code (or by using proxies and timing correlation), unless the senders use VPN or some overlay network. Tor is an adequate solution in most cases to mitigate it, but it requires additional technical knowledge to install and configure (even installing Orbot on Android is seen as "complex" by many users), and reduces usability because of higher latency.
The lack of in-built IP address protection is the main concern of many users, particularly given that most people do not realise that it is lacking by default - without transport protection SimpleX is not perceived as a "whole product".
The lack of in-built IP address protection is the main concern of many users, particularly given that most people do not realize that it is lacking by default - without transport protection SimpleX is not perceived as a "whole product".
Similarly, XFTP protocol relays are chosen by senders, and they can be used to detect file recipients' IP addresses.
@@ -43,7 +43,7 @@ Overall, this is not a viable or even appropriate option for the current stage.
3. SMP / XFTP proxy.
Introduce SMP and XFTP protocol extenstions to allow message senders and file recipients to delegate the tasks of sending messages and receiving files to the proxies, so that peer-chosen relays can only observe IP addresses of the proxies and not of the users.
Introduce SMP and XFTP protocol extensions to allow message senders and file recipients to delegate the tasks of sending messages and receiving files to the proxies, so that peer-chosen relays can only observe IP addresses of the proxies and not of the users.
Pros:
- no dependency on and lower latency than via Tor
@@ -68,7 +68,7 @@ Below considers this design.
2. SMP proxy should not be able to observe queue addresses and their count on the destination relays. This requirement is not needed for XFTP proxies, as each file chunk is downloaded only once, so there is no need to hide its address.
3. There must be no identifiers and cyphertext in common in outgoing and incoming traffic inside TLS (the current designs have this quality).
3. There must be no identifiers and ciphertext in common in outgoing and incoming traffic inside TLS (the current designs have this quality).
4. Traffic between the client and destination relays must be e2e encrypted, with MITM-by-proxy mitigated, relying on the relay identity (certificate fingerprint), ideally without any additional fingerprint in relay address.
@@ -97,11 +97,11 @@ This would also reduce the difference in how the traffic looks to the observer -
The flow of the messages will be:
1. Client requests proxy to create session with the relay by sending `server` command with the SMP relay address and optional proxy basic AUTH (below). It should be possible to batch multiple session requests into one block, to reduce traffic.
1. Client requests proxy to create session with the relay by sending `PRXY` command with the SMP relay address and optional proxy basic AUTH (below). It should be possible to batch multiple session requests into one block, to reduce traffic.
2. Proxy connects to SMP relay, negotiating a shared secret in the handshake that will be used to encrypt all sender blocks inside TLS (proxy-relay encryption). SMP relay also returns in handshake its temporary DH key to agree e2e encryption with the client (sender-relay encryption, to hide metadata sent to the destination relay from proxy).
2. Proxy connects to SMP relay, negotiating a shared secret via a handshake headers - it will be used to encrypt all sender blocks inside TLS (proxy-relay encryption). DH key returned by SMP relay in handshake will also be used to encrypt client commands, combining it with random per-command keys (sender-relay encryption, to hide metadata sent to the destination relay from proxy).
3. Proxy replies with `server_id` command including relay session ID to identify it in further requests, relay DH key for e2e encryption with the client - this key is signed with the TLS online private key associated with the certificate (its fingerprint is included in the relay address), and the TLS session ID between proxy and relay (this session ID must be used in transmissions, to mitigate replay attacks as before).
3. Proxy replies to sender with `PKEY` message using "entityId" transmission field to indicate session ID for using in further requests, relay DH key for _s2r_ encryption with the client - this key is signed with the TLS online private key associated with the certificate (its fingerprint is included in the relay address), and the TLS session ID between proxy and relay (this session ID must be used in transmissions, to mitigate replay attacks as before).
A possible attack here is that proxy can use this TLS session to replay commands received from the client. Possibly, it could be mitigated with a bloom filter per proxy/SMP relay connection that would reject the repeated DH keys (that need to be used for replay), and also with DH key expiration (this mitigation should allow some acceptable rate of false positives from the bloom filter).
@@ -113,11 +113,11 @@ It is important that the same public key from destination relay is returned to a
*Unrelated cosideration for SMP protocol privacy improvement*: instead of signing commands to the destination relay, the sender could have a ratchet per queue agreed with the destination relay that would simply use authenticated encryption with per-message symmetric key to encrypt the message on the way to relay, and this encryption would be used as a proof of sender.
4. Now the client sends `forward` to proxy, which it then forwards to SMP relay, applying additional encryption layer.
4. Now the client sends `PFWD` to proxy, which it then forwards to SMP relay as `RFWD`, applying _p2r_ encryption layer.
5. SMP relay sends `response` to proxy applying additional encryption layer, which it then forwards to the client removing the additional encryption layer.
5. SMP relay sends `RRES` to proxy applying _p2r_ encryption layer, which it then forwards to the client as `PRES`, removing the _p2r_ encryption layer.
Effectively it works as a simplified two-hop onion routing with the first relay (proxy) chosen by the sending client and the second relay chosen by the recipient, not only protecting senders' IP addresses from the recipients' relays, but also preventing recipients relays from correlating senders' traffic to different queues, as TLS session is owned by the proxy now and it mixes the traffic from multiple senders. To correlate traffic to users, proxy and relay would have to combine their information. SMP relays are still able to correlate traffic to receiving users via transport session.
Effectively it works as a simplified two-hop onion routing with the first relay (proxy) chosen by the sending client and the second relay chosen by the recipient, not only protecting senders' IP addresses from the recipients' relays, but also preventing recipients' relays from correlating senders' traffic to different queues, as TLS session is owned by the proxy now and it mixes the traffic from multiple senders. To correlate traffic to users, proxy and relay would have to combine their information. SMP relays are still able to correlate traffic to receiving users via transport session.
Sequence diagram for sending the message via SMP proxy:
@@ -126,33 +126,33 @@ Sequence diagram for sending the message via SMP proxy:
| sending | | SMP | | SMP | | receiving |
| client | | proxy | | relay | | client |
------------- ------------- ------------- -------------
| `server` | | |
| -------------------------> | create TLS session, get keys | |
| `PRXY` | | |
| -------------------------> | | |
| | ------------------------------> | |
| `server_id` | (if doesn't exist) | |
| | SMP handshake | |
| | <------------------------------ | |
| `PKEY` | | |
| <------------------------- | | |
| | | |
| TLS(F:s2r(SEND(e2e(msg)))) | | |
| -------------------------> | TLS(F:p2r(s2r(SEND(e2e(msg))))) | |
| `PFWD` (s2r) | | |
| -------------------------> | | |
| | `RFWD` (p2r) | |
| | ------------------------------> | |
| | | |
| | TLS(R:p2r(s2r(OK/ERR))) | |
| TLS(R:s2r(OK/ERR)) | <------------------------------ | |
| <------------------------- | | TLS(MSG(r2c(e2e(msg)))) |
| | | -----------------------> |
| | | |
| | | TLS(ACK) |
| | `RRES` (p2r) | |
| | <------------------------------ | |
| `PRES` (s2r) | | `MSG` |
| <------------------------- | | -----------------------> |
| | | `ACK` |
| | | <----------------------- |
| | | |
| | | |
```
Below diagram shows the encrypttion layers for `forward` and `response` commands:
Below diagram shows the encrypttion layers for `PFWD`/`RFWD` commands and `RRES`/`PRES` responses:
- s2r (added) - encryption between client and SMP relay, with relay key returned in server_id command, with MITM by proxy mitigated by verifying the certificate fingerprint included in the relay address.
- s2r (added) - encryption between client and SMP relay, with relay key returned in relay handshake, with MITM by proxy mitigated by verifying the certificate fingerprint included in the relay address.
- e2e (exists now) - end-to-end encryption per SMP queue, with double ratchet e2e encryption inside it.
- p2r (added) - additional encryption between proxy and SMP relay with key agreed in the handshake, to mitigate traffic correlation inside TLS. This key could also be signed by the same certificate, if we don't want to rely on TLS security.
- p2r (added) - additional encryption between proxy and SMP relay with the shared secret agreed in the handshake, to mitigate traffic correlation inside TLS.
- r2c (exists now) additional encryption between SMP relay and client to prevent traffic correlation inside TLS.
```
@@ -167,27 +167,32 @@ Below diagram shows the encrypttion layers for `forward` and `response` commands
----------------- ----------------- -- TLS -- ----------------- -----------------
```
When proxy connects to SMP relay it would indicate in the handshake that it will use proxy protocol and the SMP relay would expect the same `forward` commands and reply with `response`s.
Question: should proxy declare its role in handshake? When proxy connects to SMP relay it would indicate in the handshake that it will act as a proxy and the SMP relay would expect the same `forward` commands and reply with `response`s.
Below syntax aims to fit in 16kb block using spare capacity in SMP protocol.
Common SMP transmission format (v4), for reference:
```abnf
proxy_block = padded(proxy_transmission, 16384)
proxy_transmission = corr_id relay_session_id proxy_command
corr_id = length *8 OCTET
proxy_command = server / server_id / forward / response / error
server = "S" address [relay_basic_auth] ; creates transport session between proxy and relay
server_id = "I" relay_session_id tls_session_id signed_relay_key ;
; session_id is the TLS session ID between proxy and relay, it has to be included inside encrypted block to prevent replay attacks
forward = %s"F" random_dh_pub_key encrypted_block ; it's important that a new key is used for each command, to prevent any correlation by proxy or by destination relay
response = %s"R" encrypted_block; response received from the destination SMP relay
relay_session_id = length *8 OCTET
error = %s"E" error
paddedTransmission = <padded(transmission), 16384>
transmission = signature signed
signature = 0 ; empty signatures here
signed = sessionIdentifier corrId entityId (smpCommand / brokerMsg)
```
The overhead is: 1+8 (corrId) + 1+8 (relay_session_id) + 1 (command) + 1+32 (random_dh_pub_key) + 2 (original length) + 16 (auth tag for e2e encryption) + 16 (auth tag for proxy to relay encryption) = 86 bytes. The reserve for sent messages in SMP is ~84 bytes, so it should about fit with some reduced bytes somewhere.
- `corrId` is fully random each time and used as a nonce for encrypted blocks.
- `entityId` carries tlsUniq from the current proxy-to-relay connection.
- `smpCommand` gets extended with `s2p_command / p2r_command`.
- `brokerMsg` gets extended with `r_key / r_response`.
Another possible design is to allow mixing sent messages and normal SMP commands in the same transport connection, but it can make fitting in the block a bit harder, additional overhead would be: 1 (transmission count) + 2 (transmission size) + 1 (empty signature) = 4 bytes.
```abnf
s2p_command = proxy / forward
p2r_command = p_handshake ; forward is
proxy = %s"PRXY" SP relayUri SP basicAuth
relayUri = length %s"smp://" serverIdentity "@" srvHost [":" port]
forward = %s"PFWD" SP dhPublic SP encryptedBlock
r_key = %s"PKEY" SP dhPublic
r_response = %s"RRES" SP encryptedBlock
dhPublic = length x509encoded
```
The above assumes that the client can only send one message to an SMP relay and then has to wait for response before sending the next message. Missing the response would cause re-delivery (further improvement is possible when proxy detects these redelieveries and not send them to relays but simply reply with the same response).
+1
View File
@@ -651,6 +651,7 @@ test-suite simplexmq-test
ServerTests
SMPAgentClient
SMPClient
SMPProxyTests
Util
XFTPAgent
XFTPCLI
+2
View File
@@ -756,6 +756,8 @@ data Signature (a :: Algorithm) where
SignatureEd25519 :: Ed25519.Signature -> Signature Ed25519
SignatureEd448 :: Ed448.Signature -> Signature Ed448
deriving instance Eq (Signature a)
deriving instance Show (Signature a)
data ASignature
+79 -1
View File
@@ -188,7 +188,9 @@ import Data.String
import Data.Time.Clock.System (SystemTime (..))
import Data.Type.Equality
import Data.Word (Word16)
import qualified Data.X509 as X
import GHC.TypeLits (ErrorMessage (..), TypeError, type (+))
import qualified GHC.TypeLits as TE
import Network.Socket (ServiceName)
import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Encoding
@@ -359,6 +361,17 @@ data Command (p :: Party) where
PING :: Command Sender
-- SMP notification subscriber commands
NSUB :: Command Notifier
PRXY :: SMPServer -> Maybe BasicAuth -> Command Sender -- request a relay server connection by URI
-- Transmission to proxy:
-- - entity ID: ID of the session with relay returned in PKEY (response to PRXY)
-- - corrId: also used as a nonce to encrypt transmission to relay, corrId + 1 - from relay
-- - key (1st param in the command) is used to agree DH secret for this particular transmission and its response
-- Encrypted transmission should include session ID (tlsunique) from proxy-relay connection.
PFWD :: C.PublicKeyX25519 -> EncTransmission -> Command Sender -- use CorrId as CbNonce, client to proxy
-- Transmission forwarded to relay:
-- - entity ID: empty
-- - corrId: unique correlation ID between proxy and relay, also used as a nonce to encrypt forwarded transmission
RFWD :: EncFwdTransmission -> Command Sender -- use CorrId as CbNonce, proxy to relay
deriving instance Show (Command p)
@@ -384,6 +397,18 @@ instance Encoding SubscriptionMode where
'C' -> pure SMOnlyCreate
_ -> fail "bad SubscriptionMode"
newtype EncTransmission = EncTransmission ByteString
deriving (Show)
data FwdTransmission = FwdTransmission
{ fwdCorrId :: ByteString,
fwdKey :: C.PublicKeyX25519,
fwdTransmission :: ByteString
}
newtype EncFwdTransmission = EncFwdTransmission ByteString
deriving (Show)
data BrokerMsg where
-- SMP broker messages (responses, client messages, notifications)
IDS :: QueueIdsKeys -> BrokerMsg
@@ -393,6 +418,10 @@ data BrokerMsg where
MSG :: RcvMessage -> BrokerMsg
NID :: NotifierId -> RcvNtfPublicDhKey -> BrokerMsg
NMSG :: C.CbNonce -> EncNMsgMeta -> BrokerMsg
-- Should include certificate chain
PKEY :: X.CertificateChain -> X.SignedExact X.PubKey -> BrokerMsg -- TLS-signed server key for proxy shared secret and initial sender key
RRES :: EncFwdResponse -> BrokerMsg -- relay to proxy
PRES :: EncResponse -> BrokerMsg -- proxy to client
END :: BrokerMsg
OK :: BrokerMsg
ERR :: ErrorType -> BrokerMsg
@@ -405,6 +434,17 @@ data RcvMessage = RcvMessage
}
deriving (Eq, Show)
newtype EncFwdResponse = EncFwdResponse ByteString
deriving (Eq, Show)
data FwdResponse = FwdResponse
{ fwdCorrId :: ByteString,
fwdResponse :: ByteString
}
newtype EncResponse = EncResponse ByteString
deriving (Eq, Show)
-- | received message without server/recipient encryption
data Message
= Message
@@ -567,6 +607,9 @@ data CommandTag (p :: Party) where
DEL_ :: CommandTag Recipient
SEND_ :: CommandTag Sender
PING_ :: CommandTag Sender
PRXY_ :: CommandTag Sender
PFWD_ :: CommandTag Sender
RFWD_ :: CommandTag Sender
NSUB_ :: CommandTag Notifier
data CmdTag = forall p. PartyI p => CT (SParty p) (CommandTag p)
@@ -580,6 +623,9 @@ data BrokerMsgTag
| MSG_
| NID_
| NMSG_
| PKEY_
| RRES_
| PRES_
| END_
| OK_
| ERR_
@@ -607,6 +653,9 @@ instance PartyI p => Encoding (CommandTag p) where
DEL_ -> "DEL"
SEND_ -> "SEND"
PING_ -> "PING"
PRXY_ -> "PRXY"
PFWD_ -> "PFWD"
RFWD_ -> "RFWD"
NSUB_ -> "NSUB"
smpP = messageTagP
@@ -623,6 +672,9 @@ instance ProtocolMsgTag CmdTag where
"DEL" -> Just $ CT SRecipient DEL_
"SEND" -> Just $ CT SSender SEND_
"PING" -> Just $ CT SSender PING_
"PRXY" -> Just $ CT SSender PRXY_
"PFWD" -> Just $ CT SSender PFWD_
"RFWD" -> Just $ CT SSender RFWD_
"NSUB" -> Just $ CT SNotifier NSUB_
_ -> Nothing
@@ -639,6 +691,9 @@ instance Encoding BrokerMsgTag where
MSG_ -> "MSG"
NID_ -> "NID"
NMSG_ -> "NMSG"
PKEY_ -> "PKEY"
RRES_ -> "RRES"
PRES_ -> "PRES"
END_ -> "END"
OK_ -> "OK"
ERR_ -> "ERR"
@@ -651,6 +706,9 @@ instance ProtocolMsgTag BrokerMsgTag where
"MSG" -> Just MSG_
"NID" -> Just NID_
"NMSG" -> Just NMSG_
"PKEY" -> Just PKEY_
"RRES" -> Just RRES_
"PRES" -> Just PRES_
"END" -> Just END_
"OK" -> Just OK_
"ERR" -> Just ERR_
@@ -829,7 +887,7 @@ type family UserProtocol (p :: ProtocolType) :: Constraint where
UserProtocol PSMP = ()
UserProtocol PXFTP = ()
UserProtocol a =
(Int ~ Bool, TypeError (Text "Servers for protocol " :<>: ShowType a :<>: Text " cannot be configured by the users"))
(Int ~ Bool, TypeError (TE.Text "Servers for protocol " :<>: ShowType a :<>: TE.Text " cannot be configured by the users"))
userProtocol :: SProtocolType p -> Maybe (Dict (UserProtocol p))
userProtocol = \case
@@ -1046,6 +1104,8 @@ data ErrorType
NO_MSG
| -- | sent message is too large (> maxMessageLength = 16088 bytes)
LARGE_MSG
| -- | relay public key is expired
EXPIRED
| -- | internal server error
INTERNAL
| -- | used internally, never returned by the server (to be removed)
@@ -1135,6 +1195,9 @@ instance PartyI p => ProtocolEncoding SMPVersion ErrorType (Command p) where
SEND flags msg -> e (SEND_, ' ', flags, ' ', Tail msg)
PING -> e PING_
NSUB -> e NSUB_
PRXY host auth_ -> e (PRXY_, ' ', strEncode host, ' ', auth_)
PFWD {} -> error "TODO: e (PFWD_,,)"
RFWD {} -> error "TODO: e (RFWD_,,)"
where
e :: Encoding a => a -> ByteString
e = smpEncode
@@ -1158,6 +1221,9 @@ instance PartyI p => ProtocolEncoding SMPVersion ErrorType (Command p) where
PING
| isNothing auth && B.null queueId -> Right cmd
| otherwise -> Left $ CMD HAS_AUTH
PRXY {}
| isNothing auth && B.null queueId -> Right cmd
| otherwise -> Left $ CMD HAS_AUTH
-- other client commands must have both signature and queue ID
_
| isNothing auth || B.null queueId -> Left $ CMD NO_AUTH
@@ -1189,6 +1255,10 @@ instance ProtocolEncoding SMPVersion ErrorType Cmd where
Cmd SSender <$> case tag of
SEND_ -> SEND <$> _smpP <*> (unTail <$> _smpP)
PING_ -> pure PING
PFWD_ -> error "TODO: PFWD_"
RFWD_ -> error "TODO: RFWD_"
PRXY_ -> PRXY <$> (_smpP >>= either fail pure . strDecode) <*> _smpP
CT SNotifier NSUB_ -> pure $ Cmd SNotifier NSUB
fromProtocolError = fromProtocolError @SMPVersion @ErrorType @BrokerMsg
@@ -1204,6 +1274,9 @@ instance ProtocolEncoding SMPVersion ErrorType BrokerMsg where
e (MSG_, ' ', msgId, Tail body)
NID nId srvNtfDh -> e (NID_, ' ', nId, srvNtfDh)
NMSG nmsgNonce encNMsgMeta -> e (NMSG_, ' ', nmsgNonce, encNMsgMeta)
PKEY cert key -> e (PKEY_, ' ', C.encodeCertChain cert, C.SignedObject key)
RRES (EncFwdResponse encBlock) -> e (RRES_, ' ', Tail encBlock)
PRES (EncResponse encBlock) -> e (PRES_, ' ', Tail encBlock)
END -> e END_
OK -> e OK_
ERR err -> e (ERR_, ' ', err)
@@ -1221,6 +1294,9 @@ instance ProtocolEncoding SMPVersion ErrorType BrokerMsg where
IDS_ -> IDS <$> (QIK <$> _smpP <*> smpP <*> smpP)
NID_ -> NID <$> _smpP <*> smpP
NMSG_ -> NMSG <$> _smpP <*> smpP
PKEY_ -> PKEY <$> (A.space *> C.certChainP) <*> (C.getSignedExact <$> smpP)
RRES_ -> RRES <$> (EncFwdResponse . unTail <$> _smpP)
PRES_ -> PRES <$> (EncResponse . unTail <$> _smpP)
END_ -> pure END
OK_ -> pure OK
ERR_ -> ERR <$> _smpP
@@ -1270,6 +1346,7 @@ instance Encoding ErrorType where
CMD err -> "CMD " <> smpEncode err
AUTH -> "AUTH"
QUOTA -> "QUOTA"
EXPIRED -> "EXPIRED"
NO_MSG -> "NO_MSG"
LARGE_MSG -> "LARGE_MSG"
INTERNAL -> "INTERNAL"
@@ -1282,6 +1359,7 @@ instance Encoding ErrorType where
"CMD" -> CMD <$> _smpP
"AUTH" -> pure AUTH
"QUOTA" -> pure QUOTA
"EXPIRED" -> pure EXPIRED
"NO_MSG" -> pure NO_MSG
"LARGE_MSG" -> pure LARGE_MSG
"INTERNAL" -> pure INTERNAL
+23
View File
@@ -524,6 +524,9 @@ verifyTransmission auth_ tAuth authorized queueId cmd =
Cmd SSender PING -> pure $ VRVerified Nothing
-- NSUB will not be accepted without authorization
Cmd SNotifier NSUB -> verifyQueue (\q -> maybe dummyVerify (Just q `verifiedWith`) (notifierKey <$> notifier q)) <$> get SNotifier
Cmd SSender PRXY {} -> pure $ VRVerified Nothing
Cmd SSender PFWD {} -> pure $ VRVerified Nothing
Cmd SSender RFWD {} -> pure $ VRVerified Nothing
where
verify = verifyCmdAuthorization auth_ tAuth authorized
dummyVerify = verify (dummyAuthKey tAuth) `seq` VRFailed
@@ -597,6 +600,17 @@ client clnt@Client {subscriptions, ntfSubscriptions, rcvQ, sndQ, sessionId} Serv
case command of
SEND flags msgBody -> withQueue $ \qr -> sendMessage qr flags msgBody
PING -> pure (corrId, "", PONG)
PRXY relay auth ->
ifM
allowProxy
(setupProxy relay)
(pure (corrId, queueId, ERR AUTH))
where
allowProxy = do
ServerConfig {allowSMPProxy, newQueueBasicAuth} <- asks config
pure $ allowSMPProxy && maybe True ((== auth) . Just) newQueueBasicAuth
PFWD _dhPub _encBlock -> error "TODO: processCommand.PFWD"
RFWD _encBlock -> error "TODO: processCommand.RFWD"
Cmd SNotifier NSUB -> subscribeNotifications
Cmd SRecipient command ->
case command of
@@ -922,6 +936,15 @@ client clnt@Client {subscriptions, ntfSubscriptions, rcvQ, sndQ, sessionId} Serv
Right q -> updateDeletedStats q $> ok
Left e -> pure $ err e
setupProxy :: SMPServer -> M (Transmission BrokerMsg)
setupProxy todo'relay = undefined
-- do
-- let relaySessionId = "TODO: relaySessionId"
-- (dummyRelayDhPublic, _) <- atomically . C.generateKeyPair =<< asks random
-- (_, dummySignKey) <- atomically . C.generateKeyPair =<< asks random
-- let dummyRelayKeySignature = C.sign' dummySignKey $ smpEncode dummyRelayDhPublic
-- pure (corrId, relaySessionId, PKEY dummyRelayDhPublic dummyRelayKeySignature)
ok :: Transmission BrokerMsg
ok = (corrId, queueId, OK)
+36 -9
View File
@@ -15,12 +15,14 @@ import qualified Data.IntMap.Strict as IM
import Data.List.NonEmpty (NonEmpty)
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as M
import Data.Text (Text)
import Data.Time.Clock (getCurrentTime)
import Data.Time.Clock.System (SystemTime)
import Data.X509.Validation (Fingerprint (..))
import Network.Socket (ServiceName)
import qualified Network.TLS as T
import Numeric.Natural
import Simplex.Messaging.Agent.Env.SQLite (Worker)
import Simplex.Messaging.Agent.Lock
import Simplex.Messaging.Crypto (KeyHash (..))
import qualified Simplex.Messaging.Crypto as C
@@ -33,7 +35,7 @@ import Simplex.Messaging.Server.Stats
import Simplex.Messaging.Server.StoreLog
import Simplex.Messaging.TMap (TMap)
import qualified Simplex.Messaging.TMap as TM
import Simplex.Messaging.Transport (ATransport, VersionSMP, VersionRangeSMP)
import Simplex.Messaging.Transport (ATransport, SessionId, VersionSMP, VersionRangeSMP)
import Simplex.Messaging.Transport.Server (SocketState, TransportServerConfig, loadFingerprint, loadTLSServerParams, newSocketState)
import System.IO (IOMode (..))
import System.Mem.Weak (Weak)
@@ -79,7 +81,8 @@ data ServerConfig = ServerConfig
-- | TCP transport config
transportConfig :: TransportServerConfig,
-- | run listener on control port
controlPort :: Maybe ServiceName
controlPort :: Maybe ServiceName,
allowSMPProxy :: Bool -- auth is the same with `newQueueBasicAuth`
}
defMsgExpirationDays :: Int64
@@ -110,8 +113,9 @@ data Env = Env
tlsServerParams :: T.ServerParams,
serverStats :: ServerStats,
sockets :: SocketState,
clientSeq :: TVar Int,
clients :: TVar (IntMap Client)
clientSeq :: TVar ClientId,
clients :: TVar (IntMap Client),
proxyServer :: SMPProxyServer -- senders served on this proxy
}
data Server = Server
@@ -122,8 +126,22 @@ data Server = Server
savingLock :: Lock
}
data SMPProxyServer = SMPProxyServer
{ relaySessions :: TMap SessionId SMPProxiedRelay,
relayServers :: TMap Text SessionId -- speed up client lookups by server URI
}
data SMPProxiedRelay = SMPProxiedRelay
{ worker :: Worker,
proxyKey :: C.DhSecretX25519,
fwdQ :: TBQueue (ClientId, CorrId, C.PublicKeyX25519, ByteString) -- FWD args from multiple clients using this server
-- can be used for QUOTA retries until the session is gone
}
type ClientId = Int
data Client = Client
{ clientId :: Int,
{ clientId :: ClientId,
subscriptions :: TMap RecipientId (TVar Sub),
ntfSubscriptions :: TMap NotifierId (),
rcvQ :: TBQueue (NonEmpty (Maybe QueueRec, Transmission Cmd)),
@@ -135,7 +153,8 @@ data Client = Client
connected :: TVar Bool,
createdAt :: SystemTime,
rcvActiveAt :: TVar SystemTime,
sndActiveAt :: TVar SystemTime
sndActiveAt :: TVar SystemTime,
proxyClient_ :: TVar (Maybe C.DhSecretX25519) -- this client is actually an SMP proxy
}
data SubscriptionThread = NoSub | SubPending | SubThread (Weak ThreadId) | ProhibitSub
@@ -154,7 +173,7 @@ newServer = do
savingLock <- createLock
return Server {subscribedQ, subscribers, ntfSubscribedQ, notifiers, savingLock}
newClient :: TVar Int -> Natural -> VersionSMP -> ByteString -> SystemTime -> STM Client
newClient :: TVar ClientId -> Natural -> VersionSMP -> ByteString -> SystemTime -> STM Client
newClient nextClientId qSize thVersion sessionId createdAt = do
clientId <- stateTVar nextClientId $ \next -> (next, next + 1)
subscriptions <- TM.empty
@@ -166,7 +185,8 @@ newClient nextClientId qSize thVersion sessionId createdAt = do
connected <- newTVar True
rcvActiveAt <- newTVar createdAt
sndActiveAt <- newTVar createdAt
return Client {clientId, subscriptions, ntfSubscriptions, rcvQ, sndQ, endThreads, endThreadSeq, thVersion, sessionId, connected, createdAt, rcvActiveAt, sndActiveAt}
proxyClient_ <- newTVar Nothing
return Client {clientId, subscriptions, ntfSubscriptions, rcvQ, sndQ, endThreads, endThreadSeq, thVersion, sessionId, connected, createdAt, rcvActiveAt, sndActiveAt, proxyClient_}
newSubscription :: SubscriptionThread -> STM Sub
newSubscription subThread = do
@@ -187,7 +207,8 @@ newEnv config@ServerConfig {caCertificateFile, certificateFile, privateKeyFile,
sockets <- atomically newSocketState
clientSeq <- newTVarIO 0
clients <- newTVarIO mempty
return Env {config, server, serverIdentity, queueStore, msgStore, random, storeLog, tlsServerParams, serverStats, sockets, clientSeq, clients}
proxyServer <- newSMPProxyServer
return Env {config, server, serverIdentity, queueStore, msgStore, random, storeLog, tlsServerParams, serverStats, sockets, clientSeq, clients, proxyServer}
where
restoreQueues :: QueueStore -> FilePath -> IO (StoreLog 'WriteMode)
restoreQueues QueueStore {queues, senders, notifiers} f = do
@@ -203,3 +224,9 @@ newEnv config@ServerConfig {caCertificateFile, certificateFile, privateKeyFile,
addNotifier q = case notifier q of
Nothing -> id
Just NtfCreds {notifierId} -> M.insert notifierId (recipientId q)
newSMPProxyServer :: MonadIO m => m SMPProxyServer
newSMPProxyServer = do
relayServers <- atomically TM.empty
relaySessions <- atomically TM.empty
pure SMPProxyServer {relayServers, relaySessions}
+2 -1
View File
@@ -213,7 +213,8 @@ smpServerCLI cfgPath logPath =
defaultTransportServerConfig
{ logTLSErrors = fromMaybe False $ iniOnOff "TRANSPORT" "log_tls_errors" ini
},
controlPort = either (const Nothing) (Just . T.unpack) $ lookupValue "TRANSPORT" "control_port" ini
controlPort = either (const Nothing) (Just . T.unpack) $ lookupValue "TRANSPORT" "control_port" ini,
allowSMPProxy = True -- TODO: "get from INI"
}
data CliCommand
+4
View File
@@ -40,6 +40,7 @@ module Simplex.Messaging.Transport
basicAuthSMPVersion,
subModeSMPVersion,
authCmdsSMPVersion,
sendingProxySMPVersion,
simplexMQVersion,
smpBlockSize,
TransportConfig (..),
@@ -148,6 +149,9 @@ subModeSMPVersion = VersionSMP 6
authCmdsSMPVersion :: VersionSMP
authCmdsSMPVersion = VersionSMP 7
sendingProxySMPVersion :: VersionSMP
sendingProxySMPVersion = VersionSMP 8
currentClientSMPRelayVersion :: VersionSMP
currentClientSMPRelayVersion = VersionSMP 6
+10 -2
View File
@@ -73,7 +73,11 @@ testSMPClient = testSMPClientVR supportedClientSMPRelayVRange
testSMPClientVR :: Transport c => VersionRangeSMP -> (THandleSMP c -> IO a) -> IO a
testSMPClientVR vr client = do
Right useHost <- pure $ chooseTransportHost defaultNetworkConfig testHost
runTransportClient defaultTransportClientConfig Nothing useHost testPort (Just testKeyHash) $ \h -> do
testSMPClient_ useHost testPort vr client
testSMPClient_ :: Transport c => TransportHost -> ServiceName -> VersionRangeSMP -> (THandleSMP c -> IO a) -> IO a
testSMPClient_ host port vr client = do
runTransportClient defaultTransportClientConfig Nothing host port (Just testKeyHash) $ \h -> do
g <- C.newRandom
ks <- atomically $ C.generateKeyPair g
runExceptT (smpClientHandshake h ks testKeyHash vr) >>= \case
@@ -107,12 +111,16 @@ cfg =
certificateFile = "tests/fixtures/server.crt",
smpServerVRange = supportedServerSMPRelayVRange,
transportConfig = defaultTransportServerConfig,
controlPort = Nothing
controlPort = Nothing,
allowSMPProxy = False
}
cfgV7 :: ServerConfig
cfgV7 = cfg {smpServerVRange = mkVersionRange batchCmdsSMPVersion authCmdsSMPVersion}
proxyCfg :: ServerConfig
proxyCfg = cfg { allowSMPProxy = True }
withSmpServerStoreMsgLogOn :: HasCallStack => ATransport -> ServiceName -> (HasCallStack => ThreadId -> IO a) -> IO a
withSmpServerStoreMsgLogOn t = withSmpServerConfigOn t cfg {storeLogFile = Just testStoreLogFile, storeMsgsFile = Just testStoreMsgsFile, serverStatsBackupFile = Just testServerStatsBackupFile}
+67
View File
@@ -0,0 +1,67 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
module SMPProxyTests where
import SMPAgentClient (testSMPServer, testSMPServer2)
import SMPClient
import ServerTests (sendRecv)
import Simplex.Messaging.Protocol
import Simplex.Messaging.Server.Env.STM (ServerConfig (..))
import Simplex.Messaging.Transport
import Simplex.Messaging.Version (mkVersionRange)
import Test.Hspec
import Debug.Trace
smpProxyTests :: Spec
smpProxyTests = do
describe "server configuration" $ do
it "refuses proxy handshake unless enabled" testNoProxy
it "checks basic auth in proxy requests" testProxyAuth
xdescribe "proxy requests" $ do
xdescribe "bad relay URIs" $ do
it "host not resolved" todo
it "when SMP port blackholed" todo
it "no SMP service at host/port" todo
it "bad SMP fingerprint" todo
it "connects to relay" testProxyConnect
xit "connects to itself as a relay" todo
xit "batching proxy requests" todo
xdescribe "forwarding requests" $ do
it "sender-proxy-relay-recipient works" todo
it "similar timing for proxied and direct sends" todo
proxyVRange :: VersionRangeSMP
proxyVRange = mkVersionRange batchCmdsSMPVersion sendingProxySMPVersion
testNoProxy :: IO ()
testNoProxy = do
withSmpServerConfigOn (transport @TLS) cfg testPort2 $ \_ -> do
testSMPClient_ "127.0.0.1" testPort2 proxyVRange $ \(th :: THandleSMP TLS) -> do
(_, _, (_corrId, _entityId, reply)) <- sendRecv th (Nothing, "0", "", PRXY testSMPServer Nothing)
reply `shouldBe` Right (ERR AUTH)
testProxyAuth :: IO ()
testProxyAuth = do
withSmpServerConfigOn (transport @TLS) proxyCfgAuth testPort $ \_ -> do
testSMPClient_ "127.0.0.1" testPort proxyVRange $ \(th :: THandleSMP TLS) -> do
(_, s, (_corrId, _entityId, reply)) <- sendRecv th (Nothing, "0", "", PRXY testSMPServer2 $ Just "wrong")
traceShowM s
reply `shouldBe` Right (ERR AUTH)
where
proxyCfgAuth = proxyCfg {newQueueBasicAuth = Just "correct"}
testProxyConnect :: IO ()
testProxyConnect = do
withSmpServerConfigOn (transport @TLS) proxyCfg testPort $ \_ -> do
testSMPClient_ "127.0.0.1" testPort proxyVRange $ \(th :: THandleSMP TLS) -> do
(_, _, (_corrId, _entityId, reply)) <- sendRecv th (Nothing, "0", "", PRXY testSMPServer2 Nothing)
case reply of
Right PKEY {} -> pure ()
_ -> fail $ "bad reply: " <> show reply
todo :: IO ()
todo = do
fail "TODO"
-2
View File
@@ -930,8 +930,6 @@ instance Eq C.ASignature where
Just Refl -> s == s'
_ -> False
deriving instance Eq (C.Signature a)
syntaxTests :: ATransport -> Spec
syntaxTests (ATransport t) = do
it "unknown command" $ ("", "abcd", "1234", ('H', 'E', 'L', 'L', 'O')) >#> ("", "abcd", "1234", ERR $ CMD UNKNOWN)
+2
View File
@@ -21,6 +21,7 @@ import GHC.IO.Exception (IOException (..))
import qualified GHC.IO.Exception as IOException
import NtfServerTests (ntfServerTests)
import RemoteControl (remoteControlTests)
import SMPProxyTests (smpProxyTests)
import ServerTests
import Simplex.Messaging.Transport (TLS, Transport (..))
import Simplex.Messaging.Transport.WebSockets (WS)
@@ -59,6 +60,7 @@ main = do
describe "SMP server via WebSockets" $ serverTests (transport @WS)
describe "Notifications server" $ ntfServerTests (transport @TLS)
describe "SMP client agent" $ agentTests (transport @TLS)
describe "SMP proxy" smpProxyTests
describe "XFTP" $ do
describe "XFTP server" xftpServerTests
describe "XFTP file description" fileDescriptionTests