Compare commits

...
Author SHA1 Message Date
IC Rainbow dc36c96282 don't fork unless have proxied commands to process 2024-04-18 19:40:38 +03:00
Evgeny Poberezkin d1894574c5 envelope sizes 2024-04-18 12:57:29 +01:00
Evgeny Poberezkin d352b13b02 reduce transport block 2024-04-18 08:15:19 +01:00
Evgeny Poberezkin 9c8c1f7498 icrease test timeout 2024-04-17 22:42:04 +01:00
Evgeny Poberezkin 5ede3855ee remove unused type 2024-04-17 19:36:53 +01:00
a945cc5786 SMP proxy: server implementation (#1098)
* wip

* PRXY command

* progress

* SMP Proxy: client-level implementation (#1101)

* buildable

* encode messages

* update pkey

* fix queue types

* wrap SEND in proxy lookup

* WIP proxy client

* WIP

* post-rebase fixes

* encode something with something

* cleanup

* update

* fix nonce/corrId in batchingTests

* WIP: dig into createSMPProxySession

* agent

* test progress

* pass the test

* parameterize transport handle with transport peer to include server certificate (#1100)

* parameterize transport handle with transport peer to include server certificate

* include server certificate into THandle

* load server chain and sign key

* fix key type

* fix for 8.10

---------

Co-authored-by: Alexander Bondarenko <486682+dpwiz@users.noreply.github.com>
Co-authored-by: IC Rainbow <aenor.realm@gmail.com>

* cleanup

* add 2-server test

* remove subsumed test

* checkCredentials for BrokerMsg

* skip batching tests

* remove userId param

* remove agent changes

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>

---------

Co-authored-by: Alexander Bondarenko <486682+dpwiz@users.noreply.github.com>
2024-04-17 19:31:07 +01:00
Evgeny Poberezkin 11a68f4f15 Merge branch 'proxy' into ep/proxy-client-server 2024-04-15 13:48:38 +01:00
ad4b5b6b71 parameterize transport handle with transport peer to include server certificate (#1100)
* parameterize transport handle with transport peer to include server certificate

* include server certificate into THandle

* load server chain and sign key

* fix key type

* fix for 8.10

---------

Co-authored-by: Alexander Bondarenko <486682+dpwiz@users.noreply.github.com>
Co-authored-by: IC Rainbow <aenor.realm@gmail.com>
2024-04-15 13:47:48 +01:00
Evgeny Poberezkin c48277441a Merge branch 'proxy' into ep/proxy-client-server 2024-04-13 18:51:36 +01:00
Evgeny Poberezkin 98eb2742bc Merge branch 'master' into proxy 2024-04-13 18:51:17 +01:00
Evgeny Poberezkin a40c1ba50c Merge branch 'master' into ep/proxy-client-server 2024-04-13 15:45:22 +01:00
Evgeny Poberezkin b404f84b40 SMP proxy: low level client and server implementation 2024-04-12 18:07:19 +01:00
Alexander BondarenkoandEvgeny Poberezkin 875ddd80d6 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 <evgeny@poberezkin.com>
2024-04-12 18:04:29 +01:00
29 changed files with 976 additions and 284 deletions
+1 -1
View File
@@ -49,7 +49,7 @@ jobs:
run: cabal build --enable-tests run: cabal build --enable-tests
- name: Test - name: Test
timeout-minutes: 30 timeout-minutes: 40
shell: bash shell: bash
run: cabal test --test-show-details=direct run: cabal test --test-show-details=direct
+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 ## 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. 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. 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: Pros:
- no dependency on and lower latency than via Tor - 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. 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. 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: 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). 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. *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: 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 | | sending | | SMP | | SMP | | receiving |
| client | | proxy | | relay | | client | | client | | proxy | | relay | | client |
------------- ------------- ------------- ------------- ------------- ------------- ------------- -------------
| `server` | | | | `PRXY` | | |
| -------------------------> | create TLS session, get keys | | | -------------------------> | | |
| | ------------------------------> | | | | ------------------------------> | |
| `server_id` | (if doesn't exist) | | | | SMP handshake | |
| | <------------------------------ | |
| `PKEY` | | |
| <------------------------- | | | | <------------------------- | | |
| | | | | | | |
| TLS(F:s2r(SEND(e2e(msg)))) | | | | `PFWD` (s2r) | | |
| -------------------------> | TLS(F:p2r(s2r(SEND(e2e(msg))))) | | | -------------------------> | | |
| | `RFWD` (p2r) | |
| | ------------------------------> | | | | ------------------------------> | |
| | | | | | `RRES` (p2r) | |
| | TLS(R:p2r(s2r(OK/ERR))) | | | | <------------------------------ | |
| TLS(R:s2r(OK/ERR)) | <------------------------------ | | | `PRES` (s2r) | | `MSG` |
| <------------------------- | | TLS(MSG(r2c(e2e(msg)))) | | <------------------------- | | -----------------------> |
| | | -----------------------> | | | | `ACK` |
| | | |
| | | TLS(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. - 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. - 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 -- ----------------- ----------------- ----------------- ----------------- -- 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 ```abnf
proxy_block = padded(proxy_transmission, 16384) paddedTransmission = <padded(transmission), 16384>
proxy_transmission = corr_id relay_session_id proxy_command transmission = signature signed
corr_id = length *8 OCTET signature = 0 ; empty signatures here
proxy_command = server / server_id / forward / response / error signed = sessionIdentifier corrId entityId (smpCommand / brokerMsg)
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
``` ```
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). 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
@@ -652,6 +652,7 @@ test-suite simplexmq-test
ServerTests ServerTests
SMPAgentClient SMPAgentClient
SMPClient SMPClient
SMPProxyTests
Util Util
XFTPAgent XFTPAgent
XFTPCLI XFTPCLI
+11 -6
View File
@@ -51,7 +51,7 @@ import Simplex.Messaging.Protocol
RecipientId, RecipientId,
SenderId, SenderId,
) )
import Simplex.Messaging.Transport (ALPN, HandshakeError (VERSION), THandleAuth (..), THandleParams (..), TransportError (..), supportedParameters) import Simplex.Messaging.Transport (ALPN, HandshakeError (VERSION), THandleAuth (..), THandleParams (..), TransportError (..), TransportPeer (..), supportedParameters)
import Simplex.Messaging.Transport.Client (TransportClientConfig, TransportHost, alpn) import Simplex.Messaging.Transport.Client (TransportClientConfig, TransportHost, alpn)
import Simplex.Messaging.Transport.HTTP2 import Simplex.Messaging.Transport.HTTP2
import Simplex.Messaging.Transport.HTTP2.Client import Simplex.Messaging.Transport.HTTP2.Client
@@ -64,7 +64,7 @@ import UnliftIO.Directory
data XFTPClient = XFTPClient data XFTPClient = XFTPClient
{ http2Client :: HTTP2Client, { http2Client :: HTTP2Client,
transportSession :: TransportSession FileResponse, transportSession :: TransportSession FileResponse,
thParams :: THandleParams XFTPVersion, thParams :: THandleParams XFTPVersion 'TClient,
config :: XFTPClientConfig config :: XFTPClientConfig
} }
@@ -120,19 +120,21 @@ getXFTPClient g transportSession@(_, srv, _) config@XFTPClientConfig {clientALPN
atomically $ writeTVar clientVar $ Just c atomically $ writeTVar clientVar $ Just c
pure c pure c
xftpClientHandshakeV1 :: TVar ChaChaDRG -> VersionRangeXFTP -> C.KeyHash -> HTTP2Client -> THandleParamsXFTP -> ExceptT XFTPClientError IO THandleParamsXFTP xftpClientHandshakeV1 :: TVar ChaChaDRG -> VersionRangeXFTP -> C.KeyHash -> HTTP2Client -> THandleParamsXFTP 'TClient -> ExceptT XFTPClientError IO (THandleParamsXFTP 'TClient)
xftpClientHandshakeV1 g serverVRange keyHash@(C.KeyHash kh) c@HTTP2Client {sessionId, serverKey} thParams0 = do xftpClientHandshakeV1 g serverVRange keyHash@(C.KeyHash kh) c@HTTP2Client {sessionId, serverKey} thParams0 = do
shs <- getServerHandshake shs@XFTPServerHandshake {authPubKey = ck} <- getServerHandshake
(v, sk) <- processServerHandshake shs (v, sk) <- processServerHandshake shs
(k, pk) <- atomically $ C.generateKeyPair g (k, pk) <- atomically $ C.generateKeyPair g
sendClientHandshake XFTPClientHandshake {xftpVersion = v, keyHash, authPubKey = k} sendClientHandshake XFTPClientHandshake {xftpVersion = v, keyHash, authPubKey = k}
pure thParams0 {thAuth = Just THandleAuth {peerPubKey = sk, privKey = pk}, thVersion = v} pure thParams0 {thAuth = Just THAuthClient {serverPeerPubKey = sk, serverCertKey = ck, clientPrivKey = pk}, thVersion = v}
where where
getServerHandshake :: ExceptT XFTPClientError IO XFTPServerHandshake
getServerHandshake = do getServerHandshake = do
let helloReq = H.requestNoBody "POST" "/" [] let helloReq = H.requestNoBody "POST" "/" []
HTTP2Response {respBody = HTTP2Body {bodyHead = shsBody}} <- HTTP2Response {respBody = HTTP2Body {bodyHead = shsBody}} <-
liftError' (const $ PCEResponseError HANDSHAKE) $ sendRequest c helloReq Nothing liftError' (const $ PCEResponseError HANDSHAKE) $ sendRequest c helloReq Nothing
liftHS . smpDecode =<< liftHS (C.unPad shsBody) liftHS . smpDecode =<< liftHS (C.unPad shsBody)
processServerHandshake :: XFTPServerHandshake -> ExceptT XFTPClientError IO (VersionXFTP, C.PublicKeyX25519)
processServerHandshake XFTPServerHandshake {xftpVersionRange, sessionId = serverSessId, authPubKey = serverAuth} = do processServerHandshake XFTPServerHandshake {xftpVersionRange, sessionId = serverSessId, authPubKey = serverAuth} = do
unless (sessionId == serverSessId) $ throwError $ PCEResponseError SESSION unless (sessionId == serverSessId) $ throwError $ PCEResponseError SESSION
case xftpVersionRange `compatibleVersion` serverVRange of case xftpVersionRange `compatibleVersion` serverVRange of
@@ -145,6 +147,7 @@ xftpClientHandshakeV1 g serverVRange keyHash@(C.KeyHash kh) c@HTTP2Client {sessi
_ -> throwError "bad certificate" _ -> throwError "bad certificate"
pubKey <- maybe (throwError "bad server key type") (`C.verifyX509` exact) serverKey pubKey <- maybe (throwError "bad server key type") (`C.verifyX509` exact) serverKey
C.x509ToPublic (pubKey, []) >>= C.pubKey C.x509ToPublic (pubKey, []) >>= C.pubKey
sendClientHandshake :: XFTPClientHandshake -> ExceptT XFTPClientError IO ()
sendClientHandshake chs = do sendClientHandshake chs = do
chs' <- liftHS $ C.pad (smpEncode chs) xftpBlockSize chs' <- liftHS $ C.pad (smpEncode chs) xftpBlockSize
let chsReq = H.requestBuilder "POST" "/" [] $ byteString chs' let chsReq = H.requestBuilder "POST" "/" [] $ byteString chs'
@@ -183,9 +186,11 @@ xftpClientError = \case
sendXFTPCommand :: forall p. FilePartyI p => XFTPClient -> C.APrivateAuthKey -> XFTPFileId -> FileCommand p -> Maybe XFTPChunkSpec -> ExceptT XFTPClientError IO (FileResponse, HTTP2Body) sendXFTPCommand :: forall p. FilePartyI p => XFTPClient -> C.APrivateAuthKey -> XFTPFileId -> FileCommand p -> Maybe XFTPChunkSpec -> ExceptT XFTPClientError IO (FileResponse, HTTP2Body)
sendXFTPCommand c@XFTPClient {thParams} pKey fId cmd chunkSpec_ = do sendXFTPCommand c@XFTPClient {thParams} pKey fId cmd chunkSpec_ = do
-- TODO random corrId
let corrIdUsedAsNonce = ""
t <- t <-
liftEither . first PCETransportError $ liftEither . first PCETransportError $
xftpEncodeAuthTransmission thParams pKey ("", fId, FileCmd (sFileParty @p) cmd) xftpEncodeAuthTransmission thParams pKey (corrIdUsedAsNonce, fId, FileCmd (sFileParty @p) cmd)
sendXFTPTransmission c t chunkSpec_ sendXFTPTransmission c t chunkSpec_
sendXFTPTransmission :: XFTPClient -> ByteString -> Maybe XFTPChunkSpec -> ExceptT XFTPClientError IO (FileResponse, HTTP2Body) sendXFTPTransmission :: XFTPClient -> ByteString -> Maybe XFTPChunkSpec -> ExceptT XFTPClientError IO (FileResponse, HTTP2Body)
+8 -7
View File
@@ -39,8 +39,8 @@ import Simplex.Messaging.Protocol
ProtocolErrorType (..), ProtocolErrorType (..),
ProtocolMsgTag (..), ProtocolMsgTag (..),
ProtocolType (..), ProtocolType (..),
RcvPublicDhKey,
RcvPublicAuthKey, RcvPublicAuthKey,
RcvPublicDhKey,
RecipientId, RecipientId,
SenderId, SenderId,
SentRawTransmission, SentRawTransmission,
@@ -48,14 +48,15 @@ import Simplex.Messaging.Protocol
SndPublicAuthKey, SndPublicAuthKey,
Transmission, Transmission,
TransmissionForAuth (..), TransmissionForAuth (..),
encodeTransmissionForAuth, CorrId (..),
encodeTransmission, encodeTransmission,
encodeTransmissionForAuth,
messageTagP, messageTagP,
tDecodeParseValidate, tDecodeParseValidate,
tEncodeBatch1, tEncodeBatch1,
tParse, tParse,
) )
import Simplex.Messaging.Transport (THandleParams (..), TransportError (..)) import Simplex.Messaging.Transport (THandleParams (..), TransportError (..), TransportPeer (..))
import Simplex.Messaging.Util ((<$?>)) import Simplex.Messaging.Util ((<$?>))
xftpBlockSize :: Int xftpBlockSize :: Int
@@ -325,12 +326,12 @@ checkParty' c = case testEquality (sFileParty @p) (sFileParty @p') of
Just Refl -> Just c Just Refl -> Just c
_ -> Nothing _ -> Nothing
xftpEncodeAuthTransmission :: ProtocolEncoding XFTPVersion e c => THandleParams XFTPVersion -> C.APrivateAuthKey -> Transmission c -> Either TransportError ByteString xftpEncodeAuthTransmission :: ProtocolEncoding XFTPVersion e c => THandleParams XFTPVersion 'TClient -> C.APrivateAuthKey -> Transmission c -> Either TransportError ByteString
xftpEncodeAuthTransmission thParams@THandleParams {thAuth} pKey (corrId, fId, msg) = do xftpEncodeAuthTransmission thParams@THandleParams {thAuth} pKey (corrId, fId, msg) = do
let TransmissionForAuth {tForAuth, tToSend} = encodeTransmissionForAuth thParams (corrId, fId, msg) let TransmissionForAuth {tForAuth, tToSend} = encodeTransmissionForAuth thParams (corrId, fId, msg)
xftpEncodeBatch1 . (,tToSend) =<< authTransmission thAuth (Just pKey) corrId tForAuth xftpEncodeBatch1 . (,tToSend) =<< authTransmission thAuth (Just pKey) (C.cbNonce $ bs corrId) tForAuth
xftpEncodeTransmission :: ProtocolEncoding XFTPVersion e c => THandleParams XFTPVersion -> Transmission c -> Either TransportError ByteString xftpEncodeTransmission :: ProtocolEncoding XFTPVersion e c => THandleParams XFTPVersion p -> Transmission c -> Either TransportError ByteString
xftpEncodeTransmission thParams (corrId, fId, msg) = do xftpEncodeTransmission thParams (corrId, fId, msg) = do
let t = encodeTransmission thParams (corrId, fId, msg) let t = encodeTransmission thParams (corrId, fId, msg)
xftpEncodeBatch1 (Nothing, t) xftpEncodeBatch1 (Nothing, t)
@@ -339,7 +340,7 @@ xftpEncodeTransmission thParams (corrId, fId, msg) = do
xftpEncodeBatch1 :: SentRawTransmission -> Either TransportError ByteString xftpEncodeBatch1 :: SentRawTransmission -> Either TransportError ByteString
xftpEncodeBatch1 t = first (const TELargeMsg) $ C.pad (tEncodeBatch1 t) xftpBlockSize xftpEncodeBatch1 t = first (const TELargeMsg) $ C.pad (tEncodeBatch1 t) xftpBlockSize
xftpDecodeTransmission :: ProtocolEncoding XFTPVersion e c => THandleParams XFTPVersion -> ByteString -> Either XFTPErrorType (SignedTransmission e c) xftpDecodeTransmission :: ProtocolEncoding XFTPVersion e c => THandleParams XFTPVersion p -> ByteString -> Either XFTPErrorType (SignedTransmission e c)
xftpDecodeTransmission thParams t = do xftpDecodeTransmission thParams t = do
t' <- first (const BLOCK) $ C.unPad t t' <- first (const BLOCK) $ C.unPad t
case tParse thParams t' of case tParse thParams t' of
+8 -8
View File
@@ -56,7 +56,7 @@ import Simplex.Messaging.Server.Expiration
import Simplex.Messaging.Server.Stats import Simplex.Messaging.Server.Stats
import Simplex.Messaging.TMap (TMap) import Simplex.Messaging.TMap (TMap)
import qualified Simplex.Messaging.TMap as TM import qualified Simplex.Messaging.TMap as TM
import Simplex.Messaging.Transport (SessionId, THandleAuth (..), THandleParams (..)) import Simplex.Messaging.Transport (SessionId, THandleAuth (..), THandleParams (..), TransportPeer (..))
import Simplex.Messaging.Transport.Buffer (trimCR) import Simplex.Messaging.Transport.Buffer (trimCR)
import Simplex.Messaging.Transport.HTTP2 import Simplex.Messaging.Transport.HTTP2
import Simplex.Messaging.Transport.HTTP2.File (fileBlockSize) import Simplex.Messaging.Transport.HTTP2.File (fileBlockSize)
@@ -75,7 +75,7 @@ import qualified UnliftIO.Exception as E
type M a = ReaderT XFTPEnv IO a type M a = ReaderT XFTPEnv IO a
data XFTPTransportRequest = XFTPTransportRequest data XFTPTransportRequest = XFTPTransportRequest
{ thParams :: THandleParamsXFTP, { thParams :: THandleParamsXFTP 'TServer,
reqBody :: HTTP2Body, reqBody :: HTTP2Body,
request :: H.Request, request :: H.Request,
sendResponse :: H.Response -> IO () sendResponse :: H.Response -> IO ()
@@ -91,7 +91,7 @@ runXFTPServerBlocking started cfg = newXFTPServerEnv cfg >>= runReaderT (xftpSer
data Handshake data Handshake
= HandshakeSent C.PrivateKeyX25519 = HandshakeSent C.PrivateKeyX25519
| HandshakeAccepted THandleAuth VersionXFTP | HandshakeAccepted (THandleAuth 'TServer) VersionXFTP
xftpServer :: XFTPServerConfig -> TMVar Bool -> M () xftpServer :: XFTPServerConfig -> TMVar Bool -> M ()
xftpServer cfg@XFTPServerConfig {xftpPort, transportConfig, inactiveClientExpiration, fileExpiration} started = do xftpServer cfg@XFTPServerConfig {xftpPort, transportConfig, inactiveClientExpiration, fileExpiration} started = do
@@ -120,7 +120,7 @@ xftpServer cfg@XFTPServerConfig {xftpPort, transportConfig, inactiveClientExpira
Nothing -> pure () -- handshake response sent Nothing -> pure () -- handshake response sent
Just thParams -> processRequest req0 {thParams} -- proceed with new version (XXX: may as well switch the request handler here) Just thParams -> processRequest req0 {thParams} -- proceed with new version (XXX: may as well switch the request handler here)
_ -> liftIO . sendResponse $ H.responseNoBody N.ok200 [] -- shouldn't happen: means server picked handshake protocol it doesn't know about _ -> liftIO . sendResponse $ H.responseNoBody N.ok200 [] -- shouldn't happen: means server picked handshake protocol it doesn't know about
xftpServerHandshakeV1 :: X.CertificateChain -> C.APrivateSignKey -> TMap SessionId Handshake -> XFTPTransportRequest -> M (Maybe (THandleParams XFTPVersion)) xftpServerHandshakeV1 :: X.CertificateChain -> C.APrivateSignKey -> TMap SessionId Handshake -> XFTPTransportRequest -> M (Maybe (THandleParams XFTPVersion 'TServer))
xftpServerHandshakeV1 chain serverSignKey sessions XFTPTransportRequest {thParams = thParams@THandleParams {sessionId}, reqBody = HTTP2Body {bodyHead}, sendResponse} = do xftpServerHandshakeV1 chain serverSignKey sessions XFTPTransportRequest {thParams = thParams@THandleParams {sessionId}, reqBody = HTTP2Body {bodyHead}, sendResponse} = do
s <- atomically $ TM.lookup sessionId sessions s <- atomically $ TM.lookup sessionId sessions
r <- runExceptT $ case s of r <- runExceptT $ case s of
@@ -138,18 +138,18 @@ xftpServer cfg@XFTPServerConfig {xftpPort, transportConfig, inactiveClientExpira
shs <- encodeXftp hs shs <- encodeXftp hs
liftIO . sendResponse $ H.responseBuilder N.ok200 [] shs liftIO . sendResponse $ H.responseBuilder N.ok200 [] shs
pure Nothing pure Nothing
processClientHandshake privKey = do processClientHandshake pk = do
unless (B.length bodyHead == xftpBlockSize) $ throwError HANDSHAKE unless (B.length bodyHead == xftpBlockSize) $ throwError HANDSHAKE
body <- liftHS $ C.unPad bodyHead body <- liftHS $ C.unPad bodyHead
XFTPClientHandshake {xftpVersion, keyHash, authPubKey} <- liftHS $ smpDecode body XFTPClientHandshake {xftpVersion, keyHash, authPubKey} <- liftHS $ smpDecode body
kh <- asks serverIdentity kh <- asks serverIdentity
unless (keyHash == kh) $ throwError HANDSHAKE unless (keyHash == kh) $ throwError HANDSHAKE
unless (xftpVersion `isCompatible` supportedFileServerVRange) $ throwError HANDSHAKE unless (xftpVersion `isCompatible` supportedFileServerVRange) $ throwError HANDSHAKE
let auth = THandleAuth {peerPubKey = authPubKey, privKey} let auth = THAuthServer {clientPeerPubKey = authPubKey, serverPrivKey = pk}
atomically $ TM.insert sessionId (HandshakeAccepted auth xftpVersion) sessions atomically $ TM.insert sessionId (HandshakeAccepted auth xftpVersion) sessions
liftIO . sendResponse $ H.responseNoBody N.ok200 [] liftIO . sendResponse $ H.responseNoBody N.ok200 []
pure Nothing pure Nothing
sendError :: XFTPErrorType -> M (Maybe (THandleParams XFTPVersion)) sendError :: XFTPErrorType -> M (Maybe (THandleParams XFTPVersion 'TServer))
sendError err = do sendError err = do
runExceptT (encodeXftp err) >>= \case runExceptT (encodeXftp err) >>= \case
Right bs -> liftIO . sendResponse $ H.responseBuilder N.ok200 [] bs Right bs -> liftIO . sendResponse $ H.responseBuilder N.ok200 [] bs
@@ -326,7 +326,7 @@ processRequest XFTPTransportRequest {thParams, reqBody = body@HTTP2Body {bodyHea
data VerificationResult = VRVerified XFTPRequest | VRFailed data VerificationResult = VRVerified XFTPRequest | VRFailed
verifyXFTPTransmission :: Maybe (THandleAuth, C.CbNonce) -> Maybe TransmissionAuth -> ByteString -> XFTPFileId -> FileCmd -> M VerificationResult verifyXFTPTransmission :: Maybe (THandleAuth 'TServer, C.CbNonce) -> Maybe TransmissionAuth -> ByteString -> XFTPFileId -> FileCmd -> M VerificationResult
verifyXFTPTransmission auth_ tAuth authorized fId cmd = verifyXFTPTransmission auth_ tAuth authorized fId cmd =
case cmd of case cmd of
FileCmd SFSender (FNEW file rcps auth') -> pure $ XFTPReqNew file rcps auth' `verifyWith` sndKey file FileCmd SFSender (FNEW file rcps auth') -> pure $ XFTPReqNew file rcps auth' `verifyWith` sndKey file
+5 -4
View File
@@ -1,3 +1,4 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE LambdaCase #-} {-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiWayIf #-} {-# LANGUAGE MultiWayIf #-}
@@ -51,7 +52,7 @@ import Simplex.Messaging.Encoding
import Simplex.Messaging.Encoding.String import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Parsers import Simplex.Messaging.Parsers
import Simplex.Messaging.Protocol (CommandError) import Simplex.Messaging.Protocol (CommandError)
import Simplex.Messaging.Transport (HandshakeError (..), SessionId, THandle (..), THandleParams (..), TransportError (..)) import Simplex.Messaging.Transport (HandshakeError (..), SessionId, THandle (..), THandleParams (..), TransportError (..), TransportPeer (..))
import Simplex.Messaging.Transport.HTTP2.File import Simplex.Messaging.Transport.HTTP2.File
import Simplex.Messaging.Util (bshow) import Simplex.Messaging.Util (bshow)
import Simplex.Messaging.Version import Simplex.Messaging.Version
@@ -76,8 +77,8 @@ type VersionRangeXFTP = VersionRange XFTPVersion
pattern VersionXFTP :: Word16 -> VersionXFTP pattern VersionXFTP :: Word16 -> VersionXFTP
pattern VersionXFTP v = Version v pattern VersionXFTP v = Version v
type THandleXFTP c = THandle XFTPVersion c type THandleXFTP c p = THandle XFTPVersion c p
type THandleParamsXFTP = THandleParams XFTPVersion type THandleParamsXFTP p = THandleParams XFTPVersion p
initialXFTPVersion :: VersionXFTP initialXFTPVersion :: VersionXFTP
initialXFTPVersion = VersionXFTP 1 initialXFTPVersion = VersionXFTP 1
@@ -89,7 +90,7 @@ supportedFileServerVRange :: VersionRangeXFTP
supportedFileServerVRange = mkVersionRange initialXFTPVersion currentXFTPVersion supportedFileServerVRange = mkVersionRange initialXFTPVersion currentXFTPVersion
-- XFTP protocol does not support handshake -- XFTP protocol does not support handshake
xftpClientHandshakeStub :: c -> C.KeyPairX25519 -> C.KeyHash -> VersionRangeXFTP -> ExceptT TransportError IO (THandle XFTPVersion c) xftpClientHandshakeStub :: c -> C.KeyPairX25519 -> C.KeyHash -> VersionRangeXFTP -> ExceptT TransportError IO (THandle XFTPVersion c 'TClient)
xftpClientHandshakeStub _c _ks _keyHash _xftpVRange = throwError $ TEHandshake VERSION xftpClientHandshakeStub _c _ks _keyHash _xftpVRange = throwError $ TEHandshake VERSION
data XFTPServerHandshake = XFTPServerHandshake data XFTPServerHandshake = XFTPServerHandshake
+1 -1
View File
@@ -788,7 +788,7 @@ compatibleContactUri (CRContactUri ConnReqUriData {crAgentVRange, crSmpQueues =
AgentConfig {smpClientVRange, smpAgentVRange} <- asks config AgentConfig {smpClientVRange, smpAgentVRange} <- asks config
pure $ pure $
(,) (,)
<$> (qUri `compatibleVersion` smpClientVRange) <$> (qUri `compatibleVersion` smpClientVRange)
<*> (crAgentVRange `compatibleVersion` smpAgentVRange pqSup) <*> (crAgentVRange `compatibleVersion` smpAgentVRange pqSup)
versionPQSupport_ :: VersionSMPA -> Maybe CR.VersionE2E -> PQSupport versionPQSupport_ :: VersionSMPA -> Maybe CR.VersionE2E -> PQSupport
+140 -19
View File
@@ -54,6 +54,9 @@ module Simplex.Messaging.Client
suspendSMPQueue, suspendSMPQueue,
deleteSMPQueue, deleteSMPQueue,
deleteSMPQueues, deleteSMPQueues,
createSMPProxySession,
proxySMPMessage,
forwardSMPMessage,
sendProtocolCommand, sendProtocolCommand,
-- * Supporting types and client configuration -- * Supporting types and client configuration
@@ -69,6 +72,7 @@ module Simplex.Messaging.Client
chooseTransportHost, chooseTransportHost,
proxyUsername, proxyUsername,
temporaryClientError, temporaryClientError,
smpProxyError,
ServerTransmission, ServerTransmission,
ClientCommand, ClientCommand,
@@ -84,8 +88,8 @@ import Control.Concurrent.Async
import Control.Concurrent.STM import Control.Concurrent.STM
import Control.Exception import Control.Exception
import Control.Monad import Control.Monad
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Except import Control.Monad.Except
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.Except import Control.Monad.Trans.Except
import Crypto.Random (ChaChaDRG) import Crypto.Random (ChaChaDRG)
import qualified Data.Aeson.TH as J import qualified Data.Aeson.TH as J
@@ -98,9 +102,12 @@ import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty as L import qualified Data.List.NonEmpty as L
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import Data.Time.Clock (UTCTime (..), getCurrentTime) import Data.Time.Clock (UTCTime (..), getCurrentTime)
import qualified Data.X509 as X
import qualified Data.X509.Validation as XV
import Network.Socket (ServiceName) import Network.Socket (ServiceName)
import Numeric.Natural import Numeric.Natural
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Encoding
import Simplex.Messaging.Encoding.String import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON) import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON)
import Simplex.Messaging.Protocol import Simplex.Messaging.Protocol
@@ -110,7 +117,7 @@ import Simplex.Messaging.Transport
import Simplex.Messaging.Transport.Client (SocksProxy, TransportClientConfig (..), TransportHost (..), runTransportClient) import Simplex.Messaging.Transport.Client (SocksProxy, TransportClientConfig (..), TransportHost (..), runTransportClient)
import Simplex.Messaging.Transport.KeepAlive import Simplex.Messaging.Transport.KeepAlive
import Simplex.Messaging.Transport.WebSockets (WS) import Simplex.Messaging.Transport.WebSockets (WS)
import Simplex.Messaging.Util (bshow, raceAny_, threadDelay') import Simplex.Messaging.Util (bshow, liftEitherWith, raceAny_, threadDelay')
import Simplex.Messaging.Version import Simplex.Messaging.Version
import System.Timeout (timeout) import System.Timeout (timeout)
@@ -119,7 +126,7 @@ import System.Timeout (timeout)
-- Use 'getSMPClient' to connect to an SMP server and create a client handle. -- Use 'getSMPClient' to connect to an SMP server and create a client handle.
data ProtocolClient v err msg = ProtocolClient data ProtocolClient v err msg = ProtocolClient
{ action :: Maybe (Async ()), { action :: Maybe (Async ()),
thParams :: THandleParams v, thParams :: THandleParams v 'TClient,
sessionTs :: UTCTime, sessionTs :: UTCTime,
client_ :: PClient v err msg client_ :: PClient v err msg
} }
@@ -138,7 +145,7 @@ data PClient v err msg = PClient
msgQ :: Maybe (TBQueue (ServerTransmission v msg)) msgQ :: Maybe (TBQueue (ServerTransmission v msg))
} }
smpClientStub :: TVar ChaChaDRG -> ByteString -> VersionSMP -> Maybe THandleAuth -> STM SMPClient smpClientStub :: TVar ChaChaDRG -> ByteString -> VersionSMP -> Maybe (THandleAuth 'TClient) -> STM SMPClient
smpClientStub g sessionId thVersion thAuth = do smpClientStub g sessionId thVersion thAuth = do
connected <- newTVar False connected <- newTVar False
clientCorrId <- C.newRandomDRG g clientCorrId <- C.newRandomDRG g
@@ -387,10 +394,10 @@ getProtocolClient g transportSession@(_, srv, _) cfg@ProtocolClientConfig {qSize
raceAny_ ([send c' th, process c', receive c' th] <> [ping c' | smpPingInterval > 0]) raceAny_ ([send c' th, process c', receive c' th] <> [ping c' | smpPingInterval > 0])
`finally` disconnected c' `finally` disconnected c'
send :: Transport c => ProtocolClient v err msg -> THandle v c -> IO () send :: Transport c => ProtocolClient v err msg -> THandle v c 'TClient -> IO ()
send ProtocolClient {client_ = PClient {sndQ}} h = forever $ atomically (readTBQueue sndQ) >>= tPutLog h send ProtocolClient {client_ = PClient {sndQ}} h = forever $ atomically (readTBQueue sndQ) >>= tPutLog h
receive :: Transport c => ProtocolClient v err msg -> THandle v c -> IO () receive :: Transport c => ProtocolClient v err msg -> THandle v c 'TClient -> IO ()
receive ProtocolClient {client_ = PClient {rcvQ}} h = forever $ tGet h >>= atomically . writeTBQueue rcvQ receive ProtocolClient {client_ = PClient {rcvQ}} h = forever $ tGet h >>= atomically . writeTBQueue rcvQ
ping :: ProtocolClient v err msg -> IO () ping :: ProtocolClient v err msg -> IO ()
@@ -480,6 +487,19 @@ temporaryClientError = \case
_ -> False _ -> False
{-# INLINE temporaryClientError #-} {-# INLINE temporaryClientError #-}
-- TODO keep error params
smpProxyError :: SMPClientError -> ErrorType
smpProxyError = \case
PCEProtocolError _ -> PROXY PROTOCOL
PCEResponseError _ -> PROXY RESPONSE
PCEUnexpectedResponse _ -> PROXY UNEXPECTED
PCEResponseTimeout -> PROXY TIMEOUT
PCENetworkError -> PROXY NETWORK
PCEIncompatibleHost -> PROXY BAD_HOST
PCETransportError _ -> PROXY TRANSPORT
PCECryptoError _ -> INTERNAL
PCEIOError _ -> INTERNAL
-- | Create a new SMP queue. -- | Create a new SMP queue.
-- --
-- https://github.com/simplex-chat/simplexmq/blob/master/protocol/simplex-messaging.md#create-queue-command -- https://github.com/simplex-chat/simplexmq/blob/master/protocol/simplex-messaging.md#create-queue-command
@@ -630,6 +650,102 @@ deleteSMPQueues :: SMPClient -> NonEmpty (RcvPrivateAuthKey, RecipientId) -> IO
deleteSMPQueues = okSMPCommands DEL deleteSMPQueues = okSMPCommands DEL
{-# INLINE deleteSMPQueues #-} {-# INLINE deleteSMPQueues #-}
-- TODO picture
-- send PRXY :: SMPServer -> Maybe BasicAuth -> Command Sender
-- receives PKEY :: SessionId -> X.CertificateChain -> X.SignedExact X.PubKey -> BrokerMsg
createSMPProxySession :: SMPClient -> SMPServer -> Maybe BasicAuth -> ExceptT SMPClientError IO (SessionId, VersionSMP, C.PublicKeyX25519)
createSMPProxySession c relayServ@ProtocolServer {keyHash = C.KeyHash kh} proxyAuth =
sendSMPCommand c Nothing "" (PRXY relayServ proxyAuth) >>= \case
-- XXX: rfc says sessionId should be in the entityId of response
PKEY sId vr (chain, key) -> do
case supportedClientSMPRelayVRange `compatibleVersion` vr of
Nothing -> throwE PCEIncompatibleHost -- TODO different error
Just (Compatible v) -> liftEitherWith x509Error $ (sId,v,) <$> validateRelay chain key
r -> throwE . PCEUnexpectedResponse $ bshow r
where
x509Error :: String -> SMPClientError
x509Error _msg = PCEResponseError $ error "TODO: x509 error" -- TODO different error
validateRelay :: X.CertificateChain -> X.SignedExact X.PubKey -> Either String C.PublicKeyX25519
validateRelay (X.CertificateChain cert) exact = do
serverKey <- case cert of
[leaf, ca]
| XV.Fingerprint kh == XV.getFingerprint ca X.HashSHA256 ->
C.x509ToPublic (X.certPubKey . X.signedObject $ X.getSigned leaf, []) >>= C.pubKey
_ -> throwError "bad certificate"
pubKey <- C.verifyX509 serverKey exact
C.x509ToPublic (pubKey, []) >>= C.pubKey
-- consider how to process slow responses - is it handled somehow locally or delegated to the caller
-- this method is used in the client
-- sends PFWD :: C.PublicKeyX25519 -> EncTransmission -> Command Sender
-- receives PRES :: EncResponse -> BrokerMsg -- proxy to client
proxySMPMessage ::
SMPClient ->
-- proxy session from PKEY
SessionId ->
VersionSMP ->
C.PublicKeyX25519 ->
-- message to deliver
Maybe SndPrivateAuthKey ->
SenderId ->
MsgFlags ->
MsgBody ->
ExceptT SMPClientError IO ()
-- TODO use version
proxySMPMessage c@ProtocolClient {thParams = proxyThParams, client_ = PClient {clientCorrId = g}} sessionId _v serverKey spKey sId flags msg = do
-- prepare params
let serverThAuth = (\ta -> ta {serverPeerPubKey = serverKey}) <$> thAuth proxyThParams
serverThParams = proxyThParams {sessionId, thAuth = serverThAuth}
(cmdPubKey, cmdPrivKey) <- liftIO . atomically $ C.generateKeyPair @'C.X25519 g
let cmdSecret = C.dh' serverKey cmdPrivKey
nonce@(C.CbNonce corrId) <- liftIO . atomically $ C.randomCbNonce g
-- encode
let TransmissionForAuth {tForAuth, tToSend} = encodeTransmissionForAuth serverThParams (CorrId corrId, sId, Cmd SSender $ SEND flags msg)
auth <- liftEitherWith PCETransportError $ authTransmission serverThAuth spKey nonce tForAuth
b <- case batchTransmissions (batch serverThParams) (blockSize serverThParams) [Right (auth, tToSend)] of
[] -> throwE $ PCETransportError TELargeMsg -- some other error. Internal?
TBError e _ : _ -> throwE $ PCETransportError e -- large message error?
TBTransmission s _ : _ -> pure s
TBTransmissions s _ _ : _ -> pure s
et <- liftEitherWith PCECryptoError $ EncTransmission <$> C.cbEncrypt cmdSecret nonce b paddedProxiedMsgLength
sendProtocolCommand_ c (Just nonce) Nothing sessionId (Cmd SProxiedClient (PFWD cmdPubKey et)) >>= \case
-- TODO support PKEY + resend?
PRES (EncResponse er) -> do
t' <- liftEitherWith PCECryptoError $ C.cbDecrypt cmdSecret (C.reverseNonce nonce) er
case tParse proxyThParams t' of
t'' :| [] -> case tDecodeParseValidate proxyThParams t'' of
(_auth, _signed, (_c, _e, r)) -> case r of -- TODO: verify
Left e -> throwE $ PCEResponseError e
Right OK -> pure ()
Right (ERR e) -> throwE $ PCEProtocolError e
Right u -> throwE . PCEUnexpectedResponse $ bshow u -- possibly differentiate unexpected response from server/proxy
_ -> throwE $ PCETransportError TEBadBlock
r -> throwE . PCEUnexpectedResponse $ bshow r -- from proxy
-- this method is used in the proxy
-- sends RFWD :: EncFwdTransmission -> Command Sender
-- receives RRES :: EncFwdResponse -> BrokerMsg
-- proxy should send PRES to the client with EncResponse
forwardSMPMessage :: SMPClient -> CorrId -> C.PublicKeyX25519 -> EncTransmission -> ExceptT SMPClientError IO EncResponse
forwardSMPMessage c@ProtocolClient {thParams, client_ = PClient {clientCorrId = g}} fwdCorrId fwdKey fwdTransmission = do
-- prepare params
sessSecret <- case thAuth thParams of
Nothing -> throwError $ PCEProtocolError INTERNAL -- different error - proxy didn't pass key?
Just THAuthClient {serverPeerPubKey, clientPrivKey} -> pure $ C.dh' serverPeerPubKey clientPrivKey
nonce <- liftIO . atomically $ C.randomCbNonce g
-- wrap
let fwdT = FwdTransmission {fwdCorrId, fwdKey, fwdTransmission}
eft <- liftEitherWith PCECryptoError $ EncFwdTransmission <$> C.cbEncrypt sessSecret nonce (smpEncode fwdT) paddedForwardedMsgLength
-- send
sendProtocolCommand_ c (Just nonce) Nothing "" (Cmd SSender (RFWD eft)) >>= \case
RRES (EncFwdResponse efr) -> do
-- unwrap
r' <- liftEitherWith PCECryptoError $ C.cbDecrypt sessSecret (C.reverseNonce nonce) efr
FwdResponse {fwdCorrId = _, fwdResponse} <- liftEitherWith (const $ PCEResponseError BLOCK) $ smpDecode r'
pure fwdResponse
r -> throwE . PCEUnexpectedResponse $ bshow r
okSMPCommand :: PartyI p => Command p -> SMPClient -> C.APrivateAuthKey -> QueueId -> ExceptT SMPClientError IO () okSMPCommand :: PartyI p => Command p -> SMPClient -> C.APrivateAuthKey -> QueueId -> ExceptT SMPClientError IO ()
okSMPCommand cmd c pKey qId = okSMPCommand cmd c pKey qId =
sendSMPCommand c (Just pKey) qId cmd >>= \case sendSMPCommand c (Just pKey) qId cmd >>= \case
@@ -693,8 +809,11 @@ sendBatch c@ProtocolClient {client_ = PClient {sndQ}} b = do
-- | Send Protocol command -- | Send Protocol command
sendProtocolCommand :: forall v err msg. ProtocolEncoding v err (ProtoCommand msg) => ProtocolClient v err msg -> Maybe C.APrivateAuthKey -> EntityId -> ProtoCommand msg -> ExceptT (ProtocolClientError err) IO msg sendProtocolCommand :: forall v err msg. ProtocolEncoding v err (ProtoCommand msg) => ProtocolClient v err msg -> Maybe C.APrivateAuthKey -> EntityId -> ProtoCommand msg -> ExceptT (ProtocolClientError err) IO msg
sendProtocolCommand c@ProtocolClient {client_ = PClient {sndQ}, thParams = THandleParams {batch, blockSize}} pKey entId cmd = sendProtocolCommand c = sendProtocolCommand_ c Nothing
ExceptT $ uncurry sendRecv =<< mkTransmission c (pKey, entId, cmd)
sendProtocolCommand_ :: forall v err msg. ProtocolEncoding v err (ProtoCommand msg) => ProtocolClient v err msg -> Maybe C.CbNonce -> Maybe C.APrivateAuthKey -> EntityId -> ProtoCommand msg -> ExceptT (ProtocolClientError err) IO msg
sendProtocolCommand_ c@ProtocolClient {client_ = PClient {sndQ}, thParams = THandleParams {batch, blockSize}} nonce_ pKey entId cmd =
ExceptT $ uncurry sendRecv =<< mkTransmission_ c nonce_ (pKey, entId, cmd)
where where
-- two separate "atomically" needed to avoid blocking -- two separate "atomically" needed to avoid blocking
sendRecv :: Either TransportError SentRawTransmission -> Request err msg -> IO (Either (ProtocolClientError err) msg) sendRecv :: Either TransportError SentRawTransmission -> Request err msg -> IO (Either (ProtocolClientError err) msg)
@@ -713,33 +832,35 @@ getResponse :: ProtocolClient v err msg -> Request err msg -> IO (Response err m
getResponse ProtocolClient {client_ = PClient {tcpTimeout, pingErrorCount}} Request {entityId, responseVar} = do getResponse ProtocolClient {client_ = PClient {tcpTimeout, pingErrorCount}} Request {entityId, responseVar} = do
response <- response <-
timeout tcpTimeout (atomically (takeTMVar responseVar)) >>= \case timeout tcpTimeout (atomically (takeTMVar responseVar)) >>= \case
-- BTW: another registerDelay candidate. Also, crashes caller with BlockedIndef.
Just r -> atomically (writeTVar pingErrorCount 0) $> r Just r -> atomically (writeTVar pingErrorCount 0) $> r
Nothing -> pure $ Left PCEResponseTimeout Nothing -> pure $ Left PCEResponseTimeout
pure Response {entityId, response} pure Response {entityId, response}
mkTransmission :: forall v err msg. ProtocolEncoding v err (ProtoCommand msg) => ProtocolClient v err msg -> ClientCommand msg -> IO (PCTransmission err msg) mkTransmission :: ProtocolEncoding v err (ProtoCommand msg) => ProtocolClient v err msg -> ClientCommand msg -> IO (PCTransmission err msg)
mkTransmission ProtocolClient {thParams, client_ = PClient {clientCorrId, sentCommands}} (pKey_, entId, cmd) = do mkTransmission c = mkTransmission_ c Nothing
corrId <- atomically getNextCorrId
let TransmissionForAuth {tForAuth, tToSend} = encodeTransmissionForAuth thParams (corrId, entId, cmd) mkTransmission_ :: forall v err msg. ProtocolEncoding v err (ProtoCommand msg) => ProtocolClient v err msg -> Maybe C.CbNonce -> ClientCommand msg -> IO (PCTransmission err msg)
auth = authTransmission (thAuth thParams) pKey_ corrId tForAuth mkTransmission_ ProtocolClient {thParams, client_ = PClient {clientCorrId, sentCommands}} nonce_ (pKey_, entId, cmd) = do
r <- atomically $ mkRequest corrId nonce@(C.CbNonce corrId) <- maybe (atomically $ C.randomCbNonce clientCorrId) pure nonce_
let TransmissionForAuth {tForAuth, tToSend} = encodeTransmissionForAuth thParams (CorrId corrId, entId, cmd)
auth = authTransmission (thAuth thParams) pKey_ nonce tForAuth
r <- atomically $ mkRequest (CorrId corrId)
pure ((,tToSend) <$> auth, r) pure ((,tToSend) <$> auth, r)
where where
getNextCorrId :: STM CorrId
getNextCorrId = CorrId <$> C.randomBytes 24 clientCorrId -- also used as nonce
mkRequest :: CorrId -> STM (Request err msg) mkRequest :: CorrId -> STM (Request err msg)
mkRequest corrId = do mkRequest corrId = do
r <- Request entId <$> newEmptyTMVar r <- Request entId <$> newEmptyTMVar
TM.insert corrId r sentCommands TM.insert corrId r sentCommands
pure r pure r
authTransmission :: Maybe THandleAuth -> Maybe C.APrivateAuthKey -> CorrId -> ByteString -> Either TransportError (Maybe TransmissionAuth) authTransmission :: Maybe (THandleAuth 'TClient) -> Maybe C.APrivateAuthKey -> C.CbNonce -> ByteString -> Either TransportError (Maybe TransmissionAuth)
authTransmission thAuth pKey_ (CorrId corrId) t = traverse authenticate pKey_ authTransmission thAuth pKey_ nonce t = traverse authenticate pKey_
where where
authenticate :: C.APrivateAuthKey -> Either TransportError TransmissionAuth authenticate :: C.APrivateAuthKey -> Either TransportError TransmissionAuth
authenticate (C.APrivateAuthKey a pk) = case a of authenticate (C.APrivateAuthKey a pk) = case a of
C.SX25519 -> case thAuth of C.SX25519 -> case thAuth of
Just THandleAuth {peerPubKey} -> Right $ TAAuthenticator $ C.cbAuthenticate peerPubKey pk (C.cbNonce corrId) t Just THAuthClient {serverPeerPubKey = k} -> Right $ TAAuthenticator $ C.cbAuthenticate k pk nonce t
Nothing -> Left TENoServerAuth Nothing -> Left TENoServerAuth
C.SEd25519 -> sign pk C.SEd25519 -> sign pk
C.SEd448 -> sign pk C.SEd448 -> sign pk
+15 -6
View File
@@ -98,6 +98,7 @@ data SMPClientAgent = SMPClientAgent
agentQ :: TBQueue SMPClientAgentEvent, agentQ :: TBQueue SMPClientAgentEvent,
randomDrg :: TVar ChaChaDRG, randomDrg :: TVar ChaChaDRG,
smpClients :: TMap SMPServer SMPClientVar, smpClients :: TMap SMPServer SMPClientVar,
smpSessions :: TMap SessionId SMPClient,
srvSubs :: TMap SMPServer (TMap SMPSub C.APrivateAuthKey), srvSubs :: TMap SMPServer (TMap SMPSub C.APrivateAuthKey),
pendingSrvSubs :: TMap SMPServer (TMap SMPSub C.APrivateAuthKey), pendingSrvSubs :: TMap SMPServer (TMap SMPSub C.APrivateAuthKey),
reconnections :: TVar [Async ()], reconnections :: TVar [Async ()],
@@ -135,6 +136,7 @@ newSMPClientAgent agentCfg@SMPClientAgentConfig {msgQSize, agentQSize} randomDrg
msgQ <- newTBQueue msgQSize msgQ <- newTBQueue msgQSize
agentQ <- newTBQueue agentQSize agentQ <- newTBQueue agentQSize
smpClients <- TM.empty smpClients <- TM.empty
smpSessions <- TM.empty
srvSubs <- TM.empty srvSubs <- TM.empty
pendingSrvSubs <- TM.empty pendingSrvSubs <- TM.empty
reconnections <- newTVar [] reconnections <- newTVar []
@@ -147,6 +149,7 @@ newSMPClientAgent agentCfg@SMPClientAgentConfig {msgQSize, agentQSize} randomDrg
agentQ, agentQ,
randomDrg, randomDrg,
smpClients, smpClients,
smpSessions,
srvSubs, srvSubs,
pendingSrvSubs, pendingSrvSubs,
reconnections, reconnections,
@@ -155,7 +158,7 @@ newSMPClientAgent agentCfg@SMPClientAgentConfig {msgQSize, agentQSize} randomDrg
} }
getSMPServerClient' :: SMPClientAgent -> SMPServer -> ExceptT SMPClientError IO SMPClient getSMPServerClient' :: SMPClientAgent -> SMPServer -> ExceptT SMPClientError IO SMPClient
getSMPServerClient' ca@SMPClientAgent {agentCfg, smpClients, msgQ, randomDrg, workerSeq} srv = getSMPServerClient' ca@SMPClientAgent {agentCfg, smpClients, smpSessions, msgQ, randomDrg, workerSeq} srv =
atomically getClientVar >>= either newSMPClient waitForSMPClient atomically getClientVar >>= either newSMPClient waitForSMPClient
where where
getClientVar :: STM (Either SMPClientVar SMPClientVar) getClientVar :: STM (Either SMPClientVar SMPClientVar)
@@ -178,7 +181,9 @@ getSMPServerClient' ca@SMPClientAgent {agentCfg, smpClients, msgQ, randomDrg, wo
tryE (connectClient v) >>= \r -> case r of tryE (connectClient v) >>= \r -> case r of
Right smp -> do Right smp -> do
logInfo . decodeUtf8 $ "Agent connected to " <> showServer srv logInfo . decodeUtf8 $ "Agent connected to " <> showServer srv
atomically $ putTMVar (sessionVar v) r atomically $ do
putTMVar (sessionVar v) r
TM.insert (sessionId $ thParams smp) smp smpSessions
successAction smp successAction smp
Left e -> do Left e -> do
if e == PCENetworkError || e == PCEResponseTimeout if e == PCENetworkError || e == PCEResponseTimeout
@@ -200,13 +205,14 @@ getSMPServerClient' ca@SMPClientAgent {agentCfg, smpClients, msgQ, randomDrg, wo
connectClient v = ExceptT $ getProtocolClient randomDrg (1, srv, Nothing) (smpCfg agentCfg) (Just msgQ) (clientDisconnected v) connectClient v = ExceptT $ getProtocolClient randomDrg (1, srv, Nothing) (smpCfg agentCfg) (Just msgQ) (clientDisconnected v)
clientDisconnected :: SMPClientVar -> SMPClient -> IO () clientDisconnected :: SMPClientVar -> SMPClient -> IO ()
clientDisconnected v _ = do clientDisconnected v smp = do
removeClientAndSubs v >>= (`forM_` serverDown) removeClientAndSubs v smp >>= (`forM_` serverDown)
logInfo . decodeUtf8 $ "Agent disconnected from " <> showServer srv logInfo . decodeUtf8 $ "Agent disconnected from " <> showServer srv
removeClientAndSubs :: SMPClientVar -> IO (Maybe (Map SMPSub C.APrivateAuthKey)) removeClientAndSubs :: SMPClientVar -> SMPClient -> IO (Maybe (Map SMPSub C.APrivateAuthKey))
removeClientAndSubs v = atomically $ do removeClientAndSubs v smp = atomically $ do
removeSessVar v srv smpClients removeSessVar v srv smpClients
TM.delete (sessionId $ thParams smp) smpSessions
TM.lookupDelete srv (srvSubs ca) >>= mapM updateSubs TM.lookupDelete srv (srvSubs ca) >>= mapM updateSubs
where where
updateSubs sVar = do updateSubs sVar = do
@@ -271,6 +277,9 @@ getSMPServerClient' ca@SMPClientAgent {agentCfg, smpClients, msgQ, randomDrg, wo
notify :: SMPClientAgentEvent -> IO () notify :: SMPClientAgentEvent -> IO ()
notify evt = atomically $ writeTBQueue (agentQ ca) evt notify evt = atomically $ writeTBQueue (agentQ ca) evt
lookupSMPServerClient :: SMPClientAgent -> SessionId -> STM (Maybe SMPClient)
lookupSMPServerClient SMPClientAgent {smpSessions} sessId = TM.lookup sessId smpSessions
closeSMPClientAgent :: SMPClientAgent -> IO () closeSMPClientAgent :: SMPClientAgent -> IO ()
closeSMPClientAgent c = do closeSMPClientAgent c = do
closeSMPServerClients c closeSMPServerClients c
+6
View File
@@ -141,6 +141,7 @@ module Simplex.Messaging.Crypto
sbEncrypt_, sbEncrypt_,
cbNonce, cbNonce,
randomCbNonce, randomCbNonce,
reverseNonce,
-- * NaCl crypto_secretbox -- * NaCl crypto_secretbox
SbKey (unSbKey), SbKey (unSbKey),
@@ -756,6 +757,8 @@ data Signature (a :: Algorithm) where
SignatureEd25519 :: Ed25519.Signature -> Signature Ed25519 SignatureEd25519 :: Ed25519.Signature -> Signature Ed25519
SignatureEd448 :: Ed448.Signature -> Signature Ed448 SignatureEd448 :: Ed448.Signature -> Signature Ed448
deriving instance Eq (Signature a)
deriving instance Show (Signature a) deriving instance Show (Signature a)
data ASignature data ASignature
@@ -1290,6 +1293,9 @@ randomCbNonce = fmap CryptoBoxNonce . randomBytes 24
randomBytes :: Int -> TVar ChaChaDRG -> STM ByteString randomBytes :: Int -> TVar ChaChaDRG -> STM ByteString
randomBytes n gVar = stateTVar gVar $ randomBytesGenerate n randomBytes n gVar = stateTVar gVar $ randomBytesGenerate n
reverseNonce :: CbNonce -> CbNonce
reverseNonce (CryptoBoxNonce s) = CryptoBoxNonce (B.reverse s)
instance Encoding CbNonce where instance Encoding CbNonce where
smpEncode = unCbNonce smpEncode = unCbNonce
smpP = CryptoBoxNonce <$> A.take 24 smpP = CryptoBoxNonce <$> A.take 24
@@ -47,7 +47,7 @@ import qualified Simplex.Messaging.Protocol as SMP
import Simplex.Messaging.Server import Simplex.Messaging.Server
import Simplex.Messaging.Server.Stats import Simplex.Messaging.Server.Stats
import qualified Simplex.Messaging.TMap as TM import qualified Simplex.Messaging.TMap as TM
import Simplex.Messaging.Transport (ATransport (..), THandle (..), THandleAuth (..), THandleParams (..), TProxy, Transport (..)) import Simplex.Messaging.Transport (ATransport (..), THandle (..), THandleAuth (..), THandleParams (..), TProxy, Transport (..), TransportPeer (..))
import Simplex.Messaging.Transport.Server (runTransportServer, tlsServerCredentials) import Simplex.Messaging.Transport.Server (runTransportServer, tlsServerCredentials)
import Simplex.Messaging.Util import Simplex.Messaging.Util
import System.Exit (exitFailure) import System.Exit (exitFailure)
@@ -339,7 +339,7 @@ updateTknStatus NtfTknData {ntfTknId, tknStatus} status = do
old <- atomically $ stateTVar tknStatus (,status) old <- atomically $ stateTVar tknStatus (,status)
when (old /= status) $ withNtfLog $ \sl -> logTokenStatus sl ntfTknId status when (old /= status) $ withNtfLog $ \sl -> logTokenStatus sl ntfTknId status
runNtfClientTransport :: Transport c => THandleNTF c -> M () runNtfClientTransport :: Transport c => THandleNTF c 'TServer -> M ()
runNtfClientTransport th@THandle {params} = do runNtfClientTransport th@THandle {params} = do
qSize <- asks $ clientQSize . config qSize <- asks $ clientQSize . config
ts <- liftIO getSystemTime ts <- liftIO getSystemTime
@@ -356,7 +356,7 @@ runNtfClientTransport th@THandle {params} = do
clientDisconnected :: NtfServerClient -> IO () clientDisconnected :: NtfServerClient -> IO ()
clientDisconnected NtfServerClient {connected} = atomically $ writeTVar connected False clientDisconnected NtfServerClient {connected} = atomically $ writeTVar connected False
receive :: Transport c => THandleNTF c -> NtfServerClient -> M () receive :: Transport c => THandleNTF c 'TServer -> NtfServerClient -> M ()
receive th@THandle {params = THandleParams {thAuth}} NtfServerClient {rcvQ, sndQ, rcvActiveAt} = forever $ do receive th@THandle {params = THandleParams {thAuth}} NtfServerClient {rcvQ, sndQ, rcvActiveAt} = forever $ do
ts <- liftIO $ tGet th ts <- liftIO $ tGet th
forM_ ts $ \t@(_, _, (corrId, entId, cmdOrError)) -> do forM_ ts $ \t@(_, _, (corrId, entId, cmdOrError)) -> do
@@ -371,7 +371,7 @@ receive th@THandle {params = THandleParams {thAuth}} NtfServerClient {rcvQ, sndQ
where where
write q t = atomically $ writeTBQueue q t write q t = atomically $ writeTBQueue q t
send :: Transport c => THandleNTF c -> NtfServerClient -> IO () send :: Transport c => THandleNTF c 'TServer -> NtfServerClient -> IO ()
send h@THandle {params} NtfServerClient {sndQ, sndActiveAt} = forever $ do send h@THandle {params} NtfServerClient {sndQ, sndActiveAt} = forever $ do
t <- atomically $ readTBQueue sndQ t <- atomically $ readTBQueue sndQ
void . liftIO $ tPut h [Right (Nothing, encodeTransmission params t)] void . liftIO $ tPut h [Right (Nothing, encodeTransmission params t)]
@@ -382,7 +382,7 @@ send h@THandle {params} NtfServerClient {sndQ, sndActiveAt} = forever $ do
data VerificationResult = VRVerified NtfRequest | VRFailed data VerificationResult = VRVerified NtfRequest | VRFailed
verifyNtfTransmission :: Maybe (THandleAuth, C.CbNonce) -> SignedTransmission ErrorType NtfCmd -> NtfCmd -> M VerificationResult verifyNtfTransmission :: Maybe (THandleAuth 'TServer, C.CbNonce) -> SignedTransmission ErrorType NtfCmd -> NtfCmd -> M VerificationResult
verifyNtfTransmission auth_ (tAuth, authorized, (corrId, entId, _)) cmd = do verifyNtfTransmission auth_ (tAuth, authorized, (corrId, entId, _)) cmd = do
st <- asks store st <- asks store
case cmd of case cmd of
@@ -24,16 +24,16 @@ import Numeric.Natural
import Simplex.Messaging.Client.Agent import Simplex.Messaging.Client.Agent
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Notifications.Protocol import Simplex.Messaging.Notifications.Protocol
import Simplex.Messaging.Notifications.Transport (NTFVersion, VersionRangeNTF)
import Simplex.Messaging.Notifications.Server.Push.APNS import Simplex.Messaging.Notifications.Server.Push.APNS
import Simplex.Messaging.Notifications.Server.Stats import Simplex.Messaging.Notifications.Server.Stats
import Simplex.Messaging.Notifications.Server.Store import Simplex.Messaging.Notifications.Server.Store
import Simplex.Messaging.Notifications.Server.StoreLog import Simplex.Messaging.Notifications.Server.StoreLog
import Simplex.Messaging.Notifications.Transport (NTFVersion, VersionRangeNTF)
import Simplex.Messaging.Protocol (CorrId, SMPServer, Transmission) import Simplex.Messaging.Protocol (CorrId, SMPServer, Transmission)
import Simplex.Messaging.Server.Expiration import Simplex.Messaging.Server.Expiration
import Simplex.Messaging.TMap (TMap) import Simplex.Messaging.TMap (TMap)
import qualified Simplex.Messaging.TMap as TM import qualified Simplex.Messaging.TMap as TM
import Simplex.Messaging.Transport (ATransport, THandleParams) import Simplex.Messaging.Transport (ATransport, THandleParams, TransportPeer (..))
import Simplex.Messaging.Transport.Server (TransportServerConfig, loadFingerprint, loadTLSServerParams) import Simplex.Messaging.Transport.Server (TransportServerConfig, loadFingerprint, loadTLSServerParams)
import System.IO (IOMode (..)) import System.IO (IOMode (..))
import System.Mem.Weak (Weak) import System.Mem.Weak (Weak)
@@ -161,13 +161,13 @@ data NtfRequest
data NtfServerClient = NtfServerClient data NtfServerClient = NtfServerClient
{ rcvQ :: TBQueue NtfRequest, { rcvQ :: TBQueue NtfRequest,
sndQ :: TBQueue (Transmission NtfResponse), sndQ :: TBQueue (Transmission NtfResponse),
ntfThParams :: THandleParams NTFVersion, ntfThParams :: THandleParams NTFVersion 'TServer,
connected :: TVar Bool, connected :: TVar Bool,
rcvActiveAt :: TVar SystemTime, rcvActiveAt :: TVar SystemTime,
sndActiveAt :: TVar SystemTime sndActiveAt :: TVar SystemTime
} }
newNtfServerClient :: Natural -> THandleParams NTFVersion -> SystemTime -> STM NtfServerClient newNtfServerClient :: Natural -> THandleParams NTFVersion 'TServer -> SystemTime -> STM NtfServerClient
newNtfServerClient qSize ntfThParams ts = do newNtfServerClient qSize ntfThParams ts = do
rcvQ <- newTBQueue qSize rcvQ <- newTBQueue qSize
sndQ <- newTBQueue qSize sndQ <- newTBQueue qSize
@@ -5,6 +5,7 @@
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
module Simplex.Messaging.Notifications.Transport where module Simplex.Messaging.Notifications.Transport where
@@ -18,9 +19,9 @@ import qualified Data.X509 as X
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Encoding import Simplex.Messaging.Encoding
import Simplex.Messaging.Transport import Simplex.Messaging.Transport
import Simplex.Messaging.Util (liftEitherWith)
import Simplex.Messaging.Version import Simplex.Messaging.Version
import Simplex.Messaging.Version.Internal import Simplex.Messaging.Version.Internal
import Simplex.Messaging.Util (liftEitherWith)
ntfBlockSize :: Int ntfBlockSize :: Int
ntfBlockSize = 512 ntfBlockSize = 512
@@ -54,7 +55,7 @@ supportedClientNTFVRange = mkVersionRange initialNTFVersion currentClientNTFVers
supportedServerNTFVRange :: VersionRangeNTF supportedServerNTFVRange :: VersionRangeNTF
supportedServerNTFVRange = mkVersionRange initialNTFVersion currentServerNTFVersion supportedServerNTFVRange = mkVersionRange initialNTFVersion currentServerNTFVersion
type THandleNTF c = THandle NTFVersion c type THandleNTF c p = THandle NTFVersion c p
data NtfServerHandshake = NtfServerHandshake data NtfServerHandshake = NtfServerHandshake
{ ntfVersionRange :: VersionRangeNTF, { ntfVersionRange :: VersionRangeNTF,
@@ -111,7 +112,7 @@ encodeNtfAuthPubKey v k
| otherwise = "" | otherwise = ""
-- | Notifcations server transport handshake. -- | Notifcations server transport handshake.
ntfServerHandshake :: forall c. Transport c => C.APrivateSignKey -> c -> C.KeyPairX25519 -> C.KeyHash -> VersionRangeNTF -> ExceptT TransportError IO (THandleNTF c) ntfServerHandshake :: forall c. Transport c => C.APrivateSignKey -> c -> C.KeyPairX25519 -> C.KeyHash -> VersionRangeNTF -> ExceptT TransportError IO (THandleNTF c 'TServer)
ntfServerHandshake serverSignKey c (k, pk) kh ntfVRange = do ntfServerHandshake serverSignKey c (k, pk) kh ntfVRange = do
let th@THandle {params = THandleParams {sessionId}} = ntfTHandle c let th@THandle {params = THandleParams {sessionId}} = ntfTHandle c
let sk = C.signX509 serverSignKey $ C.publicToX509 k let sk = C.signX509 serverSignKey $ C.publicToX509 k
@@ -121,11 +122,11 @@ ntfServerHandshake serverSignKey c (k, pk) kh ntfVRange = do
| keyHash /= kh -> | keyHash /= kh ->
throwError $ TEHandshake IDENTITY throwError $ TEHandshake IDENTITY
| v `isCompatible` ntfVRange -> | v `isCompatible` ntfVRange ->
pure $ ntfThHandle th v pk k' pure $ ntfThHandleServer th v pk k'
| otherwise -> throwError $ TEHandshake VERSION | otherwise -> throwError $ TEHandshake VERSION
-- | Notifcations server client transport handshake. -- | Notifcations server client transport handshake.
ntfClientHandshake :: forall c. Transport c => c -> C.KeyPairX25519 -> C.KeyHash -> VersionRangeNTF -> ExceptT TransportError IO (THandleNTF c) ntfClientHandshake :: forall c. Transport c => c -> C.KeyPairX25519 -> C.KeyHash -> VersionRangeNTF -> ExceptT TransportError IO (THandleNTF c 'TClient)
ntfClientHandshake c (k, pk) keyHash ntfVRange = do ntfClientHandshake c (k, pk) keyHash ntfVRange = do
let th@THandle {params = THandleParams {sessionId}} = ntfTHandle c let th@THandle {params = THandleParams {sessionId}} = ntfTHandle c
NtfServerHandshake {sessionId = sessId, ntfVersionRange, authPubKey = sk'} <- getHandshake th NtfServerHandshake {sessionId = sessId, ntfVersionRange, authPubKey = sk'} <- getHandshake th
@@ -133,23 +134,32 @@ ntfClientHandshake c (k, pk) keyHash ntfVRange = do
then throwError TEBadSession then throwError TEBadSession
else case ntfVersionRange `compatibleVersion` ntfVRange of else case ntfVersionRange `compatibleVersion` ntfVRange of
Just (Compatible v) -> do Just (Compatible v) -> do
sk_ <- forM sk' $ \exact -> liftEitherWith (const $ TEHandshake BAD_AUTH) $ do ck_ <- forM sk' $ \signedKey -> liftEitherWith (const $ TEHandshake BAD_AUTH) $ do
serverKey <- getServerVerifyKey c serverKey <- getServerVerifyKey c
pubKey <- C.verifyX509 serverKey exact pubKey <- C.verifyX509 serverKey signedKey
C.x509ToPublic (pubKey, []) >>= C.pubKey (,(getServerCerts c, signedKey)) <$> (C.x509ToPublic (pubKey, []) >>= C.pubKey)
sendHandshake th $ NtfClientHandshake {ntfVersion = v, keyHash, authPubKey = Just k} sendHandshake th $ NtfClientHandshake {ntfVersion = v, keyHash, authPubKey = Just k}
pure $ ntfThHandle th v pk sk_ pure $ ntfThHandleClient th v pk ck_
Nothing -> throwError $ TEHandshake VERSION Nothing -> throwError $ TEHandshake VERSION
ntfThHandle :: forall c. THandleNTF c -> VersionNTF -> C.PrivateKeyX25519 -> Maybe C.PublicKeyX25519 -> THandleNTF c ntfThHandleServer :: forall c. THandleNTF c 'TServer -> VersionNTF -> C.PrivateKeyX25519 -> Maybe C.PublicKeyX25519 -> THandleNTF c 'TServer
ntfThHandle th@THandle {params} v privKey k_ = ntfThHandleServer th v pk k_ =
-- TODO drop SMP v6: make thAuth non-optional let thAuth = (\k -> THAuthServer {clientPeerPubKey = k, serverPrivKey = pk}) <$> k_
let thAuth = (\k -> THandleAuth {peerPubKey = k, privKey}) <$> k_ in ntfThHandle_ th v thAuth
v3 = v >= authBatchCmdsNTFVersion
params' = params {thVersion = v, thAuth, implySessId = v3, batch = v3}
in (th :: THandleNTF c) {params = params'}
ntfTHandle :: Transport c => c -> THandleNTF c ntfThHandleClient :: forall c. THandleNTF c 'TClient -> VersionNTF -> C.PrivateKeyX25519 -> Maybe (C.PublicKeyX25519, (X.CertificateChain, X.SignedExact X.PubKey)) -> THandleNTF c 'TClient
ntfThHandleClient th v pk ck_ =
let thAuth = (\(k, ck) -> THAuthClient {serverPeerPubKey = k, serverCertKey = ck, clientPrivKey = pk}) <$> ck_
in ntfThHandle_ th v thAuth
ntfThHandle_ :: forall c p. THandleNTF c p -> VersionNTF -> Maybe (THandleAuth p) -> THandleNTF c p
ntfThHandle_ th@THandle {params} v thAuth =
-- TODO drop SMP v6: make thAuth non-optional
let v3 = v >= authBatchCmdsNTFVersion
params' = params {thVersion = v, thAuth, implySessId = v3, batch = v3}
in (th :: THandleNTF c p) {params = params'}
ntfTHandle :: Transport c => c -> THandleNTF c p
ntfTHandle c = THandle {connection = c, params} ntfTHandle c = THandle {connection = c, params}
where where
params = THandleParams {sessionId = tlsUnique c, blockSize = ntfBlockSize, thVersion = VersionNTF 0, thAuth = Nothing, implySessId = False, batch = False} params = THandleParams {sessionId = tlsUnique c, blockSize = ntfBlockSize, thVersion = VersionNTF 0, thAuth = Nothing, implySessId = False, batch = False}
+215 -27
View File
@@ -43,6 +43,8 @@ module Simplex.Messaging.Protocol
( -- * SMP protocol parameters ( -- * SMP protocol parameters
supportedSMPClientVRange, supportedSMPClientVRange,
maxMessageLength, maxMessageLength,
paddedProxiedMsgLength,
paddedForwardedMsgLength,
e2eEncConfirmationLength, e2eEncConfirmationLength,
e2eEncMessageLength, e2eEncMessageLength,
@@ -56,6 +58,7 @@ module Simplex.Messaging.Protocol
SubscriptionMode (..), SubscriptionMode (..),
Party (..), Party (..),
Cmd (..), Cmd (..),
DirectParty,
BrokerMsg (..), BrokerMsg (..),
SParty (..), SParty (..),
PartyI (..), PartyI (..),
@@ -63,6 +66,7 @@ module Simplex.Messaging.Protocol
ProtocolErrorType (..), ProtocolErrorType (..),
ErrorType (..), ErrorType (..),
CommandError (..), CommandError (..),
ProxyError (..),
Transmission, Transmission,
TransmissionAuth (..), TransmissionAuth (..),
SignedTransmission, SignedTransmission,
@@ -121,6 +125,12 @@ module Simplex.Messaging.Protocol
EncNMsgMeta, EncNMsgMeta,
SMPMsgMeta (..), SMPMsgMeta (..),
NMsgMeta (..), NMsgMeta (..),
EncFwdResponse (..),
EncFwdTransmission (..),
EncResponse (..),
EncTransmission (..),
FwdResponse (..),
FwdTransmission (..),
MsgFlags (..), MsgFlags (..),
initialSMPClientVersion, initialSMPClientVersion,
userProtocol, userProtocol,
@@ -188,7 +198,10 @@ import Data.String
import Data.Time.Clock.System (SystemTime (..)) import Data.Time.Clock.System (SystemTime (..))
import Data.Type.Equality import Data.Type.Equality
import Data.Word (Word16) import Data.Word (Word16)
import qualified Data.X509 as X
import GHC.TypeLits (ErrorMessage (..), TypeError, type (+)) import GHC.TypeLits (ErrorMessage (..), TypeError, type (+))
import qualified GHC.TypeLits as TE
import qualified GHC.TypeLits as Type
import Network.Socket (ServiceName) import Network.Socket (ServiceName)
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Encoding import Simplex.Messaging.Encoding
@@ -231,6 +244,20 @@ supportedSMPClientVRange = mkVersionRange initialSMPClientVersion currentSMPClie
maxMessageLength :: Int maxMessageLength :: Int
maxMessageLength = 16088 maxMessageLength = 16088
-- without signature works with min 16151 (fails with 16150)
-- with Ed448: 16265 (fails with 16264)
-- with Ed25519: 16215 (fails with 16214)
-- with X25519: 16232 (fails with 16231)
paddedProxiedMsgLength :: Int
paddedProxiedMsgLength = 16232
-- without signature works with min 16239 (fails with 16238)
-- with Ed448: 16353 (fails with 16352)
-- with Ed25519: 16303 (fails with 16302)
-- with X25519: 16320 (fails with 16319)
paddedForwardedMsgLength :: Int
paddedForwardedMsgLength = 16320
type MaxMessageLen = 16088 type MaxMessageLen = 16088
-- 16 extra bytes: 8 for timestamp and 8 for flags (7 flags and the space, only 1 flag is currently used) -- 16 extra bytes: 8 for timestamp and 8 for flags (7 flags and the space, only 1 flag is currently used)
@@ -244,7 +271,7 @@ e2eEncMessageLength :: Int
e2eEncMessageLength = 16032 e2eEncMessageLength = 16032
-- | SMP protocol clients -- | SMP protocol clients
data Party = Recipient | Sender | Notifier data Party = Recipient | Sender | Notifier | ProxiedClient
deriving (Show) deriving (Show)
-- | Singleton types for SMP protocol clients -- | Singleton types for SMP protocol clients
@@ -252,11 +279,13 @@ data SParty :: Party -> Type where
SRecipient :: SParty Recipient SRecipient :: SParty Recipient
SSender :: SParty Sender SSender :: SParty Sender
SNotifier :: SParty Notifier SNotifier :: SParty Notifier
SProxiedClient :: SParty ProxiedClient
instance TestEquality SParty where instance TestEquality SParty where
testEquality SRecipient SRecipient = Just Refl testEquality SRecipient SRecipient = Just Refl
testEquality SSender SSender = Just Refl testEquality SSender SSender = Just Refl
testEquality SNotifier SNotifier = Just Refl testEquality SNotifier SNotifier = Just Refl
testEquality SProxiedClient SProxiedClient = Just Refl
testEquality _ _ = Nothing testEquality _ _ = Nothing
deriving instance Show (SParty p) deriving instance Show (SParty p)
@@ -269,6 +298,15 @@ instance PartyI Sender where sParty = SSender
instance PartyI Notifier where sParty = SNotifier instance PartyI Notifier where sParty = SNotifier
instance PartyI ProxiedClient where sParty = SProxiedClient
type family DirectParty (p :: Party) :: Constraint where
DirectParty Recipient = ()
DirectParty Sender = ()
DirectParty Notifier = ()
DirectParty p =
(Int ~ Bool, TypeError (Type.Text "Party " :<>: ShowType p :<>: Type.Text " is not direct"))
-- | Type for client command of any participant. -- | Type for client command of any participant.
data Cmd = forall p. PartyI p => Cmd (SParty p) (Command p) data Cmd = forall p. PartyI p => Cmd (SParty p) (Command p)
@@ -359,6 +397,17 @@ data Command (p :: Party) where
PING :: Command Sender PING :: Command Sender
-- SMP notification subscriber commands -- SMP notification subscriber commands
NSUB :: Command Notifier NSUB :: Command Notifier
PRXY :: SMPServer -> Maybe BasicAuth -> Command ProxiedClient -- 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 ProxiedClient -- 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) deriving instance Show (Command p)
@@ -384,6 +433,25 @@ instance Encoding SubscriptionMode where
'C' -> pure SMOnlyCreate 'C' -> pure SMOnlyCreate
_ -> fail "bad SubscriptionMode" _ -> fail "bad SubscriptionMode"
newtype EncTransmission = EncTransmission ByteString
deriving (Show)
data FwdTransmission = FwdTransmission
{ fwdCorrId :: CorrId,
fwdKey :: C.PublicKeyX25519,
fwdTransmission :: EncTransmission
}
instance Encoding FwdTransmission where
smpEncode FwdTransmission {fwdCorrId = CorrId corrId, fwdKey, fwdTransmission = EncTransmission t} =
smpEncode (corrId, fwdKey, Tail t)
smpP = do
(corrId, fwdKey, Tail t) <- smpP
pure FwdTransmission {fwdCorrId = CorrId corrId, fwdKey, fwdTransmission = EncTransmission t}
newtype EncFwdTransmission = EncFwdTransmission ByteString
deriving (Show)
data BrokerMsg where data BrokerMsg where
-- SMP broker messages (responses, client messages, notifications) -- SMP broker messages (responses, client messages, notifications)
IDS :: QueueIdsKeys -> BrokerMsg IDS :: QueueIdsKeys -> BrokerMsg
@@ -393,6 +461,10 @@ data BrokerMsg where
MSG :: RcvMessage -> BrokerMsg MSG :: RcvMessage -> BrokerMsg
NID :: NotifierId -> RcvNtfPublicDhKey -> BrokerMsg NID :: NotifierId -> RcvNtfPublicDhKey -> BrokerMsg
NMSG :: C.CbNonce -> EncNMsgMeta -> BrokerMsg NMSG :: C.CbNonce -> EncNMsgMeta -> BrokerMsg
-- Should include certificate chain
PKEY :: SessionId -> VersionRangeSMP -> (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 END :: BrokerMsg
OK :: BrokerMsg OK :: BrokerMsg
ERR :: ErrorType -> BrokerMsg ERR :: ErrorType -> BrokerMsg
@@ -405,6 +477,24 @@ data RcvMessage = RcvMessage
} }
deriving (Eq, Show) deriving (Eq, Show)
newtype EncFwdResponse = EncFwdResponse ByteString
deriving (Eq, Show)
data FwdResponse = FwdResponse
{ fwdCorrId :: CorrId,
fwdResponse :: EncResponse
}
instance Encoding FwdResponse where
smpEncode FwdResponse {fwdCorrId = CorrId corrId, fwdResponse = EncResponse t} =
smpEncode (corrId, Tail t)
smpP = do
(corrId, Tail t) <- smpP
pure FwdResponse {fwdCorrId = CorrId corrId, fwdResponse = EncResponse t}
newtype EncResponse = EncResponse ByteString
deriving (Eq, Show)
-- | received message without server/recipient encryption -- | received message without server/recipient encryption
data Message data Message
= Message = Message
@@ -567,6 +657,9 @@ data CommandTag (p :: Party) where
DEL_ :: CommandTag Recipient DEL_ :: CommandTag Recipient
SEND_ :: CommandTag Sender SEND_ :: CommandTag Sender
PING_ :: CommandTag Sender PING_ :: CommandTag Sender
PRXY_ :: CommandTag ProxiedClient
PFWD_ :: CommandTag ProxiedClient
RFWD_ :: CommandTag Sender
NSUB_ :: CommandTag Notifier NSUB_ :: CommandTag Notifier
data CmdTag = forall p. PartyI p => CT (SParty p) (CommandTag p) data CmdTag = forall p. PartyI p => CT (SParty p) (CommandTag p)
@@ -580,6 +673,9 @@ data BrokerMsgTag
| MSG_ | MSG_
| NID_ | NID_
| NMSG_ | NMSG_
| PKEY_
| RRES_
| PRES_
| END_ | END_
| OK_ | OK_
| ERR_ | ERR_
@@ -607,6 +703,9 @@ instance PartyI p => Encoding (CommandTag p) where
DEL_ -> "DEL" DEL_ -> "DEL"
SEND_ -> "SEND" SEND_ -> "SEND"
PING_ -> "PING" PING_ -> "PING"
PRXY_ -> "PRXY"
PFWD_ -> "PFWD"
RFWD_ -> "RFWD"
NSUB_ -> "NSUB" NSUB_ -> "NSUB"
smpP = messageTagP smpP = messageTagP
@@ -623,6 +722,9 @@ instance ProtocolMsgTag CmdTag where
"DEL" -> Just $ CT SRecipient DEL_ "DEL" -> Just $ CT SRecipient DEL_
"SEND" -> Just $ CT SSender SEND_ "SEND" -> Just $ CT SSender SEND_
"PING" -> Just $ CT SSender PING_ "PING" -> Just $ CT SSender PING_
"PRXY" -> Just $ CT SProxiedClient PRXY_
"PFWD" -> Just $ CT SProxiedClient PFWD_
"RFWD" -> Just $ CT SSender RFWD_
"NSUB" -> Just $ CT SNotifier NSUB_ "NSUB" -> Just $ CT SNotifier NSUB_
_ -> Nothing _ -> Nothing
@@ -639,6 +741,9 @@ instance Encoding BrokerMsgTag where
MSG_ -> "MSG" MSG_ -> "MSG"
NID_ -> "NID" NID_ -> "NID"
NMSG_ -> "NMSG" NMSG_ -> "NMSG"
PKEY_ -> "PKEY"
RRES_ -> "RRES"
PRES_ -> "PRES"
END_ -> "END" END_ -> "END"
OK_ -> "OK" OK_ -> "OK"
ERR_ -> "ERR" ERR_ -> "ERR"
@@ -651,6 +756,9 @@ instance ProtocolMsgTag BrokerMsgTag where
"MSG" -> Just MSG_ "MSG" -> Just MSG_
"NID" -> Just NID_ "NID" -> Just NID_
"NMSG" -> Just NMSG_ "NMSG" -> Just NMSG_
"PKEY" -> Just PKEY_
"RRES" -> Just RRES_
"PRES" -> Just PRES_
"END" -> Just END_ "END" -> Just END_
"OK" -> Just OK_ "OK" -> Just OK_
"ERR" -> Just ERR_ "ERR" -> Just ERR_
@@ -829,7 +937,7 @@ type family UserProtocol (p :: ProtocolType) :: Constraint where
UserProtocol PSMP = () UserProtocol PSMP = ()
UserProtocol PXFTP = () UserProtocol PXFTP = ()
UserProtocol a = 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 :: SProtocolType p -> Maybe (Dict (UserProtocol p))
userProtocol = \case userProtocol = \case
@@ -1038,6 +1146,8 @@ data ErrorType
SESSION SESSION
| -- | SMP command is unknown or has invalid syntax | -- | SMP command is unknown or has invalid syntax
CMD {cmdErr :: CommandError} CMD {cmdErr :: CommandError}
| -- | error from proxied relay
PROXY {proxyErr :: ProxyError}
| -- | command authorization error - bad signature or non-existing SMP queue | -- | command authorization error - bad signature or non-existing SMP queue
AUTH AUTH
| -- | SMP queue capacity is exceeded on the server | -- | SMP queue capacity is exceeded on the server
@@ -1046,6 +1156,8 @@ data ErrorType
NO_MSG NO_MSG
| -- | sent message is too large (> maxMessageLength = 16088 bytes) | -- | sent message is too large (> maxMessageLength = 16088 bytes)
LARGE_MSG LARGE_MSG
| -- | relay public key is expired
EXPIRED
| -- | internal server error | -- | internal server error
INTERNAL INTERNAL
| -- | used internally, never returned by the server (to be removed) | -- | used internally, never returned by the server (to be removed)
@@ -1055,8 +1167,12 @@ data ErrorType
instance StrEncoding ErrorType where instance StrEncoding ErrorType where
strEncode = \case strEncode = \case
CMD e -> "CMD " <> bshow e CMD e -> "CMD " <> bshow e
PROXY e -> "PROXY " <> bshow e
e -> bshow e e -> bshow e
strP = "CMD " *> (CMD <$> parseRead1) <|> parseRead1 strP =
"CMD " *> (CMD <$> parseRead1)
<|> "PROXY " *> (PROXY <$> parseRead1)
<|> parseRead1
-- | SMP command error type. -- | SMP command error type.
data CommandError data CommandError
@@ -1074,8 +1190,24 @@ data CommandError
NO_ENTITY NO_ENTITY
deriving (Eq, Read, Show) deriving (Eq, Read, Show)
-- TODO keep error params
data ProxyError
= -- | Correctly parsed SMP server ERR response.
-- This error is forwarded to the agent client as `ERR SMP err`.
PROTOCOL -- {protocolErr :: String}
| -- | Invalid server response that failed to parse.
-- Forwarded to the agent client as `ERR BROKER RESPONSE`.
RESPONSE -- {responseErr :: String}
| UNEXPECTED
| TIMEOUT
| NETWORK
| BAD_HOST
| NO_SESSION
| TRANSPORT -- {transportErr :: TransportError}
deriving (Eq, Read, Show)
-- | SMP transmission parser. -- | SMP transmission parser.
transmissionP :: THandleParams v -> Parser RawTransmission transmissionP :: THandleParams v p -> Parser RawTransmission
transmissionP THandleParams {sessionId, implySessId} = do transmissionP THandleParams {sessionId, implySessId} = do
authenticator <- smpP authenticator <- smpP
authorized <- A.takeByteString authorized <- A.takeByteString
@@ -1089,10 +1221,10 @@ transmissionP THandleParams {sessionId, implySessId} = do
command <- A.takeByteString command <- A.takeByteString
pure RawTransmission {authenticator, authorized = authorized', sessId, corrId, entityId, command} pure RawTransmission {authenticator, authorized = authorized', sessId, corrId, entityId, command}
class (ProtocolEncoding v err msg, ProtocolEncoding v err (ProtoCommand msg), Show err, Show msg) => Protocol v err msg | msg -> v, msg -> err where class (ProtocolEncoding v err msg, ProtocolEncoding v err (ProtoCommand msg), Show err, Show msg) => Protocol v err msg | msg -> v, msg -> err where
type ProtoCommand msg = cmd | cmd -> msg type ProtoCommand msg = cmd | cmd -> msg
type ProtoType msg = (sch :: ProtocolType) | sch -> msg type ProtoType msg = (sch :: ProtocolType) | sch -> msg
protocolClientHandshake :: forall c. Transport c => c -> C.KeyPairX25519 -> C.KeyHash -> VersionRange v -> ExceptT TransportError IO (THandle v c) protocolClientHandshake :: forall c. Transport c => c -> C.KeyPairX25519 -> C.KeyHash -> VersionRange v -> ExceptT TransportError IO (THandle v c 'TClient)
protocolPing :: ProtoCommand msg protocolPing :: ProtoCommand msg
protocolError :: msg -> Maybe err protocolError :: msg -> Maybe err
@@ -1135,6 +1267,9 @@ instance PartyI p => ProtocolEncoding SMPVersion ErrorType (Command p) where
SEND flags msg -> e (SEND_, ' ', flags, ' ', Tail msg) SEND flags msg -> e (SEND_, ' ', flags, ' ', Tail msg)
PING -> e PING_ PING -> e PING_
NSUB -> e NSUB_ NSUB -> e NSUB_
PRXY host auth_ -> e (PRXY_, ' ', host, auth_)
PFWD pubKey (EncTransmission s) -> e (PFWD_, ' ', pubKey, Tail s)
RFWD (EncFwdTransmission s) -> e (RFWD_, ' ', Tail s)
where where
e :: Encoding a => a -> ByteString e :: Encoding a => a -> ByteString
e = smpEncode e = smpEncode
@@ -1144,24 +1279,33 @@ instance PartyI p => ProtocolEncoding SMPVersion ErrorType (Command p) where
fromProtocolError = fromProtocolError @SMPVersion @ErrorType @BrokerMsg fromProtocolError = fromProtocolError @SMPVersion @ErrorType @BrokerMsg
{-# INLINE fromProtocolError #-} {-# INLINE fromProtocolError #-}
checkCredentials (auth, _, queueId, _) cmd = case cmd of checkCredentials (auth, _, entId, _) cmd = case cmd of
-- NEW must have signature but NOT queue ID -- NEW must have signature but NOT queue ID
NEW {} NEW {}
| isNothing auth -> Left $ CMD NO_AUTH | isNothing auth -> Left $ CMD NO_AUTH
| not (B.null queueId) -> Left $ CMD HAS_AUTH | not (B.null entId) -> Left $ CMD HAS_AUTH
| otherwise -> Right cmd | otherwise -> Right cmd
-- SEND must have queue ID, signature is not always required -- SEND must have queue ID, signature is not always required
SEND {} SEND {}
| B.null queueId -> Left $ CMD NO_ENTITY | B.null entId -> Left $ CMD NO_ENTITY
| otherwise -> Right cmd | otherwise -> Right cmd
-- PING must not have queue ID or signature PING -> noAuthCmd
PING PRXY {} -> noAuthCmd
| isNothing auth && B.null queueId -> Right cmd PFWD {}
| B.null entId -> Left $ CMD NO_ENTITY
| isNothing auth -> Right cmd
| otherwise -> Left $ CMD HAS_AUTH | otherwise -> Left $ CMD HAS_AUTH
RFWD _ -> noAuthCmd
-- other client commands must have both signature and queue ID -- other client commands must have both signature and queue ID
_ _
| isNothing auth || B.null queueId -> Left $ CMD NO_AUTH | isNothing auth || B.null entId -> Left $ CMD NO_AUTH
| otherwise -> Right cmd | otherwise -> Right cmd
where
-- command must not have entity ID (queue or session ID) or signature
noAuthCmd :: Either ErrorType (Command p)
noAuthCmd
| isNothing auth && B.null entId = Right cmd
| otherwise = Left $ CMD HAS_AUTH
instance ProtocolEncoding SMPVersion ErrorType Cmd where instance ProtocolEncoding SMPVersion ErrorType Cmd where
type Tag Cmd = CmdTag type Tag Cmd = CmdTag
@@ -1189,6 +1333,11 @@ instance ProtocolEncoding SMPVersion ErrorType Cmd where
Cmd SSender <$> case tag of Cmd SSender <$> case tag of
SEND_ -> SEND <$> _smpP <*> (unTail <$> _smpP) SEND_ -> SEND <$> _smpP <*> (unTail <$> _smpP)
PING_ -> pure PING PING_ -> pure PING
RFWD_ -> RFWD <$> (EncFwdTransmission . unTail <$> _smpP)
CT SProxiedClient tag ->
Cmd SProxiedClient <$> case tag of
PFWD_ -> PFWD <$> _smpP <*> (EncTransmission . unTail <$> smpP)
PRXY_ -> PRXY <$> _smpP <*> smpP
CT SNotifier NSUB_ -> pure $ Cmd SNotifier NSUB CT SNotifier NSUB_ -> pure $ Cmd SNotifier NSUB
fromProtocolError = fromProtocolError @SMPVersion @ErrorType @BrokerMsg fromProtocolError = fromProtocolError @SMPVersion @ErrorType @BrokerMsg
@@ -1204,6 +1353,9 @@ instance ProtocolEncoding SMPVersion ErrorType BrokerMsg where
e (MSG_, ' ', msgId, Tail body) e (MSG_, ' ', msgId, Tail body)
NID nId srvNtfDh -> e (NID_, ' ', nId, srvNtfDh) NID nId srvNtfDh -> e (NID_, ' ', nId, srvNtfDh)
NMSG nmsgNonce encNMsgMeta -> e (NMSG_, ' ', nmsgNonce, encNMsgMeta) NMSG nmsgNonce encNMsgMeta -> e (NMSG_, ' ', nmsgNonce, encNMsgMeta)
PKEY sid vr (cert, key) -> e (PKEY_, ' ', sid, vr, C.encodeCertChain cert, C.SignedObject key)
RRES (EncFwdResponse encBlock) -> e (RRES_, ' ', Tail encBlock)
PRES (EncResponse encBlock) -> e (PRES_, ' ', Tail encBlock)
END -> e END_ END -> e END_
OK -> e OK_ OK -> e OK_
ERR err -> e (ERR_, ' ', err) ERR err -> e (ERR_, ' ', err)
@@ -1221,6 +1373,9 @@ instance ProtocolEncoding SMPVersion ErrorType BrokerMsg where
IDS_ -> IDS <$> (QIK <$> _smpP <*> smpP <*> smpP) IDS_ -> IDS <$> (QIK <$> _smpP <*> smpP <*> smpP)
NID_ -> NID <$> _smpP <*> smpP NID_ -> NID <$> _smpP <*> smpP
NMSG_ -> NMSG <$> _smpP <*> smpP NMSG_ -> NMSG <$> _smpP <*> smpP
PKEY_ -> PKEY <$> _smpP <*> smpP <*> ((,) <$> C.certChainP <*> (C.getSignedExact <$> smpP))
RRES_ -> RRES <$> (EncFwdResponse . unTail <$> _smpP)
PRES_ -> PRES <$> (EncResponse . unTail <$> _smpP)
END_ -> pure END END_ -> pure END
OK_ -> pure OK OK_ -> pure OK
ERR_ -> ERR <$> _smpP ERR_ -> ERR <$> _smpP
@@ -1233,19 +1388,24 @@ instance ProtocolEncoding SMPVersion ErrorType BrokerMsg where
PEBlock -> BLOCK PEBlock -> BLOCK
{-# INLINE fromProtocolError #-} {-# INLINE fromProtocolError #-}
checkCredentials (_, _, queueId, _) cmd = case cmd of checkCredentials (_, _, entId, _) cmd = case cmd of
-- IDS response should not have queue ID -- IDS response should not have queue ID
IDS _ -> Right cmd IDS _ -> Right cmd
-- ERR response does not always have queue ID -- ERR response does not always have queue ID
ERR _ -> Right cmd ERR _ -> Right cmd
-- PONG response must not have queue ID -- PONG response must not have queue ID
PONG PONG -> noEntityMsg
| B.null queueId -> Right cmd PKEY {} -> noEntityMsg
| otherwise -> Left $ CMD HAS_AUTH RRES _ -> noEntityMsg
-- other broker responses must have queue ID -- other broker responses must have queue ID
_ _
| B.null queueId -> Left $ CMD NO_ENTITY | B.null entId -> Left $ CMD NO_ENTITY
| otherwise -> Right cmd | otherwise -> Right cmd
where
noEntityMsg :: Either ErrorType BrokerMsg
noEntityMsg
| B.null entId = Right cmd
| otherwise = Left $ CMD HAS_AUTH
-- | Parse SMP protocol commands and broker messages -- | Parse SMP protocol commands and broker messages
parseProtocol :: forall v err msg. ProtocolEncoding v err msg => Version v -> ByteString -> Either err msg parseProtocol :: forall v err msg. ProtocolEncoding v err msg => Version v -> ByteString -> Either err msg
@@ -1268,8 +1428,10 @@ instance Encoding ErrorType where
BLOCK -> "BLOCK" BLOCK -> "BLOCK"
SESSION -> "SESSION" SESSION -> "SESSION"
CMD err -> "CMD " <> smpEncode err CMD err -> "CMD " <> smpEncode err
PROXY err -> "PROXY " <> smpEncode err
AUTH -> "AUTH" AUTH -> "AUTH"
QUOTA -> "QUOTA" QUOTA -> "QUOTA"
EXPIRED -> "EXPIRED"
NO_MSG -> "NO_MSG" NO_MSG -> "NO_MSG"
LARGE_MSG -> "LARGE_MSG" LARGE_MSG -> "LARGE_MSG"
INTERNAL -> "INTERNAL" INTERNAL -> "INTERNAL"
@@ -1280,8 +1442,10 @@ instance Encoding ErrorType where
"BLOCK" -> pure BLOCK "BLOCK" -> pure BLOCK
"SESSION" -> pure SESSION "SESSION" -> pure SESSION
"CMD" -> CMD <$> _smpP "CMD" -> CMD <$> _smpP
"PROXY" -> PROXY <$> _smpP
"AUTH" -> pure AUTH "AUTH" -> pure AUTH
"QUOTA" -> pure QUOTA "QUOTA" -> pure QUOTA
"EXPIRED" -> pure EXPIRED
"NO_MSG" -> pure NO_MSG "NO_MSG" -> pure NO_MSG
"LARGE_MSG" -> pure LARGE_MSG "LARGE_MSG" -> pure LARGE_MSG
"INTERNAL" -> pure INTERNAL "INTERNAL" -> pure INTERNAL
@@ -1304,11 +1468,33 @@ instance Encoding CommandError where
"NO_AUTH" -> pure NO_AUTH "NO_AUTH" -> pure NO_AUTH
"HAS_AUTH" -> pure HAS_AUTH "HAS_AUTH" -> pure HAS_AUTH
"NO_ENTITY" -> pure NO_ENTITY "NO_ENTITY" -> pure NO_ENTITY
"NO_QUEUE" -> pure NO_ENTITY "NO_QUEUE" -> pure NO_ENTITY -- for backward compatibility
_ -> fail "bad command error type"
instance Encoding ProxyError where
smpEncode e = case e of
PROTOCOL -> "PROTOCOL"
RESPONSE -> "RESPONSE"
UNEXPECTED -> "UNEXPECTED"
TIMEOUT -> "TIMEOUT"
NETWORK -> "NETWORK"
BAD_HOST -> "BAD_HOST"
NO_SESSION -> "NO_SESSION"
TRANSPORT -> "TRANSPORT"
smpP =
A.takeTill (== ' ') >>= \case
"PROTOCOL" -> pure PROTOCOL
"RESPONSE" -> pure RESPONSE
"UNEXPECTED" -> pure UNEXPECTED
"TIMEOUT" -> pure TIMEOUT
"NETWORK" -> pure NETWORK
"BAD_HOST" -> pure BAD_HOST
"NO_SESSION" -> pure NO_SESSION
"TRANSPORT" -> pure TRANSPORT
_ -> fail "bad command error type" _ -> fail "bad command error type"
-- | Send signed SMP transmission to TCP transport. -- | Send signed SMP transmission to TCP transport.
tPut :: Transport c => THandle v c -> NonEmpty (Either TransportError SentRawTransmission) -> IO [Either TransportError ()] tPut :: Transport c => THandle v c p -> NonEmpty (Either TransportError SentRawTransmission) -> IO [Either TransportError ()]
tPut th@THandle {params} = fmap concat . mapM tPutBatch . batchTransmissions (batch params) (blockSize params) tPut th@THandle {params} = fmap concat . mapM tPutBatch . batchTransmissions (batch params) (blockSize params)
where where
tPutBatch :: TransportBatch () -> IO [Either TransportError ()] tPutBatch :: TransportBatch () -> IO [Either TransportError ()]
@@ -1317,7 +1503,7 @@ tPut th@THandle {params} = fmap concat . mapM tPutBatch . batchTransmissions (ba
TBTransmissions s n _ -> replicate n <$> tPutLog th s TBTransmissions s n _ -> replicate n <$> tPutLog th s
TBTransmission s _ -> (: []) <$> tPutLog th s TBTransmission s _ -> (: []) <$> tPutLog th s
tPutLog :: Transport c => THandle v c -> ByteString -> IO (Either TransportError ()) tPutLog :: Transport c => THandle v c p -> ByteString -> IO (Either TransportError ())
tPutLog th s = do tPutLog th s = do
r <- tPutBlock th s r <- tPutBlock th s
case r of case r of
@@ -1383,7 +1569,7 @@ tEncodeBatch1 t = lenEncode 1 `B.cons` tEncodeForBatch t
-- tForAuth is lazy to avoid computing it when there is no key to sign -- tForAuth is lazy to avoid computing it when there is no key to sign
data TransmissionForAuth = TransmissionForAuth {tForAuth :: ~ByteString, tToSend :: ByteString} data TransmissionForAuth = TransmissionForAuth {tForAuth :: ~ByteString, tToSend :: ByteString}
encodeTransmissionForAuth :: ProtocolEncoding v e c => THandleParams v -> Transmission c -> TransmissionForAuth encodeTransmissionForAuth :: ProtocolEncoding v e c => THandleParams v p -> Transmission c -> TransmissionForAuth
encodeTransmissionForAuth THandleParams {thVersion = v, sessionId, implySessId} t = encodeTransmissionForAuth THandleParams {thVersion = v, sessionId, implySessId} t =
TransmissionForAuth {tForAuth, tToSend = if implySessId then t' else tForAuth} TransmissionForAuth {tForAuth, tToSend = if implySessId then t' else tForAuth}
where where
@@ -1391,7 +1577,7 @@ encodeTransmissionForAuth THandleParams {thVersion = v, sessionId, implySessId}
t' = encodeTransmission_ v t t' = encodeTransmission_ v t
{-# INLINE encodeTransmissionForAuth #-} {-# INLINE encodeTransmissionForAuth #-}
encodeTransmission :: ProtocolEncoding v e c => THandleParams v -> Transmission c -> ByteString encodeTransmission :: ProtocolEncoding v e c => THandleParams v p -> Transmission c -> ByteString
encodeTransmission THandleParams {thVersion = v, sessionId, implySessId} t = encodeTransmission THandleParams {thVersion = v, sessionId, implySessId} t =
if implySessId then t' else smpEncode sessionId <> t' if implySessId then t' else smpEncode sessionId <> t'
where where
@@ -1404,11 +1590,11 @@ encodeTransmission_ v (CorrId corrId, queueId, command) =
{-# INLINE encodeTransmission_ #-} {-# INLINE encodeTransmission_ #-}
-- | Receive and parse transmission from the TCP transport (ignoring any trailing padding). -- | Receive and parse transmission from the TCP transport (ignoring any trailing padding).
tGetParse :: Transport c => THandle v c -> IO (NonEmpty (Either TransportError RawTransmission)) tGetParse :: Transport c => THandle v c p -> IO (NonEmpty (Either TransportError RawTransmission))
tGetParse th@THandle {params} = eitherList (tParse params) <$> tGetBlock th tGetParse th@THandle {params} = eitherList (tParse params) <$> tGetBlock th
{-# INLINE tGetParse #-} {-# INLINE tGetParse #-}
tParse :: THandleParams v -> ByteString -> NonEmpty (Either TransportError RawTransmission) tParse :: THandleParams v p -> ByteString -> NonEmpty (Either TransportError RawTransmission)
tParse thParams@THandleParams {batch} s tParse thParams@THandleParams {batch} s
| batch = eitherList (L.map (\(Large t) -> tParse1 t)) ts | batch = eitherList (L.map (\(Large t) -> tParse1 t)) ts
| otherwise = [tParse1 s] | otherwise = [tParse1 s]
@@ -1420,10 +1606,10 @@ eitherList :: (a -> NonEmpty (Either e b)) -> Either e a -> NonEmpty (Either e b
eitherList = either (\e -> [Left e]) eitherList = either (\e -> [Left e])
-- | Receive client and server transmissions (determined by `cmd` type). -- | Receive client and server transmissions (determined by `cmd` type).
tGet :: forall v err cmd c. (ProtocolEncoding v err cmd, Transport c) => THandle v c -> IO (NonEmpty (SignedTransmission err cmd)) tGet :: forall v err cmd c p. (ProtocolEncoding v err cmd, Transport c) => THandle v c p -> IO (NonEmpty (SignedTransmission err cmd))
tGet th@THandle {params} = L.map (tDecodeParseValidate params) <$> tGetParse th tGet th@THandle {params} = L.map (tDecodeParseValidate params) <$> tGetParse th
tDecodeParseValidate :: forall v err cmd. ProtocolEncoding v err cmd => THandleParams v -> Either TransportError RawTransmission -> SignedTransmission err cmd tDecodeParseValidate :: forall v p err cmd. ProtocolEncoding v err cmd => THandleParams v p -> Either TransportError RawTransmission -> SignedTransmission err cmd
tDecodeParseValidate THandleParams {sessionId, thVersion = v, implySessId} = \case tDecodeParseValidate THandleParams {sessionId, thVersion = v, implySessId} = \case
Right RawTransmission {authenticator, authorized, sessId, corrId, entityId, command} Right RawTransmission {authenticator, authorized, sessId, corrId, entityId, command}
| implySessId || sessId == sessionId -> | implySessId || sessId == sessionId ->
@@ -1444,4 +1630,6 @@ $(J.deriveJSON defaultJSON ''MsgFlags)
$(J.deriveJSON (sumTypeJSON id) ''CommandError) $(J.deriveJSON (sumTypeJSON id) ''CommandError)
$(J.deriveJSON (sumTypeJSON id) ''ProxyError)
$(J.deriveJSON (sumTypeJSON id) ''ErrorType) $(J.deriveJSON (sumTypeJSON id) ''ErrorType)
+142 -38
View File
@@ -13,7 +13,6 @@
{-# LANGUAGE RankNTypes #-} {-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-} {-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
-- | -- |
-- Module : Simplex.Messaging.Server -- Module : Simplex.Messaging.Server
@@ -43,6 +42,7 @@ import Control.Monad
import Control.Monad.Except import Control.Monad.Except
import Control.Monad.IO.Unlift import Control.Monad.IO.Unlift
import Control.Monad.Reader import Control.Monad.Reader
import Control.Monad.Trans.Except
import Crypto.Random import Crypto.Random
import Data.Bifunctor (first) import Data.Bifunctor (first)
import Data.ByteString.Base64 (encode) import Data.ByteString.Base64 (encode)
@@ -54,6 +54,7 @@ import Data.Functor (($>))
import Data.Int (Int64) import Data.Int (Int64)
import qualified Data.IntMap.Strict as IM import qualified Data.IntMap.Strict as IM
import Data.List (intercalate) import Data.List (intercalate)
import Data.List.NonEmpty (NonEmpty)
import qualified Data.List.NonEmpty as L import qualified Data.List.NonEmpty as L
import qualified Data.Map.Strict as M import qualified Data.Map.Strict as M
import Data.Maybe (isNothing) import Data.Maybe (isNothing)
@@ -67,8 +68,10 @@ import GHC.Stats (getRTSStats)
import GHC.TypeLits (KnownNat) import GHC.TypeLits (KnownNat)
import Network.Socket (ServiceName, Socket, socketToHandle) import Network.Socket (ServiceName, Socket, socketToHandle)
import Simplex.Messaging.Agent.Lock import Simplex.Messaging.Agent.Lock
import Simplex.Messaging.Client (ProtocolClient (thParams), forwardSMPMessage, smpProxyError)
import Simplex.Messaging.Client.Agent (SMPClientAgent (..), SMPClientAgentEvent (..), getSMPServerClient', lookupSMPServerClient)
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Encoding (Encoding (smpEncode)) import Simplex.Messaging.Encoding
import Simplex.Messaging.Encoding.String import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Protocol import Simplex.Messaging.Protocol
import Simplex.Messaging.Server.Control import Simplex.Messaging.Server.Control
@@ -90,6 +93,7 @@ import System.Exit (exitFailure)
import System.IO (hPrint, hPutStrLn, hSetNewlineMode, universalNewlineMode) import System.IO (hPrint, hPutStrLn, hSetNewlineMode, universalNewlineMode)
import System.Mem.Weak (deRefWeak) import System.Mem.Weak (deRefWeak)
import UnliftIO (timeout) import UnliftIO (timeout)
import UnliftIO.Async (mapConcurrently)
import UnliftIO.Concurrent import UnliftIO.Concurrent
import UnliftIO.Directory (doesFileExist, renameFile) import UnliftIO.Directory (doesFileExist, renameFile)
import UnliftIO.Exception import UnliftIO.Exception
@@ -122,11 +126,13 @@ type M a = ReaderT Env IO a
smpServer :: TMVar Bool -> ServerConfig -> M () smpServer :: TMVar Bool -> ServerConfig -> M ()
smpServer started cfg@ServerConfig {transports, transportConfig = tCfg} = do smpServer started cfg@ServerConfig {transports, transportConfig = tCfg} = do
s <- asks server s <- asks server
pa <- asks proxyAgent
expired <- restoreServerMessages expired <- restoreServerMessages
restoreServerStats expired restoreServerStats expired
raceAny_ raceAny_
( serverThread s "server subscribedQ" subscribedQ subscribers subscriptions cancelSub ( serverThread s "server subscribedQ" subscribedQ subscribers subscriptions cancelSub
: serverThread s "server ntfSubscribedQ" ntfSubscribedQ Env.notifiers ntfSubscriptions (\_ -> pure ()) : serverThread s "server ntfSubscribedQ" ntfSubscribedQ Env.notifiers ntfSubscriptions (\_ -> pure ())
: receiveFromProxyAgent pa
: map runServer transports <> expireMessagesThread_ cfg <> serverStatsThread_ cfg <> controlPortThread_ cfg : map runServer transports <> expireMessagesThread_ cfg <> serverStatsThread_ cfg <> controlPortThread_ cfg
) )
`finally` withLock' (savingLock s) "final" (saveServer False) `finally` withLock' (savingLock s) "final" (saveServer False)
@@ -179,6 +185,19 @@ smpServer started cfg@ServerConfig {transports, transportConfig = tCfg} = do
mkWeakThreadId t >>= atomically . modifyTVar' (endThreads c) . IM.insert tId mkWeakThreadId t >>= atomically . modifyTVar' (endThreads c) . IM.insert tId
atomically $ TM.lookupDelete qId (clientSubs c) atomically $ TM.lookupDelete qId (clientSubs c)
receiveFromProxyAgent :: ProxyAgent -> M ()
receiveFromProxyAgent ProxyAgent {smpAgent = SMPClientAgent {agentQ}} =
forever $
atomically (readTBQueue agentQ) >>= \case
CAConnected srv -> logInfo $ "SMP server connected " <> showServer' srv
CADisconnected srv [] -> logInfo $ "SMP server disconnected " <> showServer' srv
CADisconnected srv subs -> logError $ "SMP server disconnected " <> showServer' srv <> " / subscriptions: " <> tshow (length subs)
CAReconnected srv -> logInfo $ "SMP server reconnected " <> showServer' srv
CAResubscribed srv subs -> logError $ "SMP server resubscribed " <> showServer' srv <> " / subscriptions: " <> tshow (length subs)
CASubError srv errs -> logError $ "SMP server subscription errors " <> showServer' srv <> " / errors: " <> tshow (length errs)
where
showServer' = decodeLatin1 . strEncode . host
expireMessagesThread_ :: ServerConfig -> [M ()] expireMessagesThread_ :: ServerConfig -> [M ()]
expireMessagesThread_ ServerConfig {messageExpiration = Just msgExp} = [expireMessages msgExp] expireMessagesThread_ ServerConfig {messageExpiration = Just msgExp} = [expireMessages msgExp]
expireMessagesThread_ _ = [] expireMessagesThread_ _ = []
@@ -314,7 +333,7 @@ smpServer started cfg@ServerConfig {transports, transportConfig = tCfg} = do
CPResume -> withAdminRole $ hPutStrLn h "resume not implemented" CPResume -> withAdminRole $ hPutStrLn h "resume not implemented"
CPClients -> withAdminRole $ do CPClients -> withAdminRole $ do
active <- unliftIO u (asks clients) >>= readTVarIO active <- unliftIO u (asks clients) >>= readTVarIO
hPutStrLn h $ "clientId,sessionId,connected,createdAt,rcvActiveAt,sndActiveAt,age,subscriptions" hPutStrLn h "clientId,sessionId,connected,createdAt,rcvActiveAt,sndActiveAt,age,subscriptions"
forM_ (IM.toList active) $ \(cid, Client {sessionId, connected, createdAt, rcvActiveAt, sndActiveAt, subscriptions}) -> do forM_ (IM.toList active) $ \(cid, Client {sessionId, connected, createdAt, rcvActiveAt, sndActiveAt, subscriptions}) -> do
connected' <- bshow <$> readTVarIO connected connected' <- bshow <$> readTVarIO connected
rcvActiveAt' <- strEncode <$> readTVarIO rcvActiveAt rcvActiveAt' <- strEncode <$> readTVarIO rcvActiveAt
@@ -409,8 +428,8 @@ smpServer started cfg@ServerConfig {transports, transportConfig = tCfg} = do
logError "Unauthorized control port command" logError "Unauthorized control port command"
hPutStrLn h "AUTH" hPutStrLn h "AUTH"
runClientTransport :: Transport c => THandleSMP c -> M () runClientTransport :: Transport c => THandleSMP c 'TServer -> M ()
runClientTransport th@THandle {params = THandleParams {thVersion, sessionId}} = do runClientTransport th@THandle {params = thParams@THandleParams {thVersion, sessionId}} = do
q <- asks $ tbqSize . config q <- asks $ tbqSize . config
ts <- liftIO getSystemTime ts <- liftIO getSystemTime
active <- asks clients active <- asks clients
@@ -422,7 +441,7 @@ runClientTransport th@THandle {params = THandleParams {thVersion, sessionId}} =
s <- asks server s <- asks server
expCfg <- asks $ inactiveClientExpiration . config expCfg <- asks $ inactiveClientExpiration . config
labelMyThread . B.unpack $ "client $" <> encode sessionId labelMyThread . B.unpack $ "client $" <> encode sessionId
raceAny_ ([liftIO $ send th c, client c s, receive th c] <> disconnectThread_ c expCfg) raceAny_ ([liftIO $ send th c, client thParams c s, receive th c] <> disconnectThread_ c expCfg)
`finally` clientDisconnected c `finally` clientDisconnected c
where where
disconnectThread_ c (Just expCfg) = [liftIO $ disconnectTransport th (rcvActiveAt c) (sndActiveAt c) expCfg (noSubscriptions c)] disconnectThread_ c (Just expCfg) = [liftIO $ disconnectTransport th (rcvActiveAt c) (sndActiveAt c) expCfg (noSubscriptions c)]
@@ -457,28 +476,28 @@ cancelSub sub =
Sub {subThread = SubThread t} -> liftIO $ deRefWeak t >>= mapM_ killThread Sub {subThread = SubThread t} -> liftIO $ deRefWeak t >>= mapM_ killThread
_ -> return () _ -> return ()
receive :: Transport c => THandleSMP c -> Client -> M () receive :: Transport c => THandleSMP c 'TServer -> Client -> M ()
receive th@THandle {params = THandleParams {thAuth}} Client {rcvQ, sndQ, rcvActiveAt, sessionId} = do receive th@THandle {params = THandleParams {thAuth}} Client {rcvQ, sndQ, rcvActiveAt, sessionId} = do
labelMyThread . B.unpack $ "client $" <> encode sessionId <> " receive" labelMyThread . B.unpack $ "client $" <> encode sessionId <> " receive"
forever $ do forever $ do
ts <- L.toList <$> liftIO (tGet th) ts <- L.toList <$> liftIO (tGet th)
atomically . writeTVar rcvActiveAt =<< liftIO getSystemTime atomically . writeTVar rcvActiveAt =<< liftIO getSystemTime
as <- partitionEithers <$> mapM cmdAction ts (errs, cmds) <- partitionEithers <$> mapM cmdAction ts
write sndQ $ fst as write sndQ errs
write rcvQ $ snd as write rcvQ cmds
where where
cmdAction :: SignedTransmission ErrorType Cmd -> M (Either (Transmission BrokerMsg) (Maybe QueueRec, Transmission Cmd)) cmdAction :: SignedTransmission ErrorType Cmd -> M (Either (Transmission BrokerMsg) (Maybe QueueRec, Transmission Cmd))
cmdAction (tAuth, authorized, (corrId, queueId, cmdOrError)) = cmdAction (tAuth, authorized, (corrId, entId, cmdOrError)) =
case cmdOrError of case cmdOrError of
Left e -> pure $ Left (corrId, queueId, ERR e) Left e -> pure $ Left (corrId, entId, ERR e)
Right cmd -> verified <$> verifyTransmission ((,C.cbNonce (bs corrId)) <$> thAuth) tAuth authorized queueId cmd Right cmd -> verified <$> verifyTransmission ((,C.cbNonce (bs corrId)) <$> thAuth) tAuth authorized entId cmd
where where
verified = \case verified = \case
VRVerified qr -> Right (qr, (corrId, queueId, cmd)) VRVerified qr -> Right (qr, (corrId, entId, cmd))
VRFailed -> Left (corrId, queueId, ERR AUTH) VRFailed -> Left (corrId, entId, ERR AUTH)
write q = mapM_ (atomically . writeTBQueue q) . L.nonEmpty write q = mapM_ (atomically . writeTBQueue q) . L.nonEmpty
send :: Transport c => THandleSMP c -> Client -> IO () send :: Transport c => THandleSMP c 'TServer -> Client -> IO ()
send h@THandle {params} Client {sndQ, sessionId, sndActiveAt} = do send h@THandle {params} Client {sndQ, sessionId, sndActiveAt} = do
labelMyThread . B.unpack $ "client $" <> encode sessionId <> " send" labelMyThread . B.unpack $ "client $" <> encode sessionId <> " send"
forever $ do forever $ do
@@ -493,7 +512,7 @@ send h@THandle {params} Client {sndQ, sessionId, sndActiveAt} = do
NMSG {} -> 0 NMSG {} -> 0
_ -> 1 _ -> 1
disconnectTransport :: Transport c => THandle v c -> TVar SystemTime -> TVar SystemTime -> ExpirationConfig -> IO Bool -> IO () disconnectTransport :: Transport c => THandle v c 'TServer -> TVar SystemTime -> TVar SystemTime -> ExpirationConfig -> IO Bool -> IO ()
disconnectTransport THandle {connection, params = THandleParams {sessionId}} rcvActiveAt sndActiveAt expCfg noSubscriptions = do disconnectTransport THandle {connection, params = THandleParams {sessionId}} rcvActiveAt sndActiveAt expCfg noSubscriptions = do
labelMyThread . B.unpack $ "client $" <> encode sessionId <> " disconnectTransport" labelMyThread . B.unpack $ "client $" <> encode sessionId <> " disconnectTransport"
loop loop
@@ -514,7 +533,7 @@ data VerificationResult = VRVerified (Maybe QueueRec) | VRFailed
-- - the queue or party key do not exist. -- - the queue or party key do not exist.
-- In all cases, the time of the verification should depend only on the provided authorization type, -- In all cases, the time of the verification should depend only on the provided authorization type,
-- a dummy key is used to run verification in the last two cases, and failure is returned irrespective of the result. -- a dummy key is used to run verification in the last two cases, and failure is returned irrespective of the result.
verifyTransmission :: Maybe (THandleAuth, C.CbNonce) -> Maybe TransmissionAuth -> ByteString -> QueueId -> Cmd -> M VerificationResult verifyTransmission :: Maybe (THandleAuth 'TServer, C.CbNonce) -> Maybe TransmissionAuth -> ByteString -> QueueId -> Cmd -> M VerificationResult
verifyTransmission auth_ tAuth authorized queueId cmd = verifyTransmission auth_ tAuth authorized queueId cmd =
case cmd of case cmd of
Cmd SRecipient (NEW k _ _ _) -> pure $ Nothing `verifiedWith` k Cmd SRecipient (NEW k _ _ _) -> pure $ Nothing `verifiedWith` k
@@ -522,21 +541,23 @@ verifyTransmission auth_ tAuth authorized queueId cmd =
-- SEND will be accepted without authorization before the queue is secured with KEY command -- SEND will be accepted without authorization before the queue is secured with KEY command
Cmd SSender SEND {} -> verifyQueue (\q -> Just q `verified` maybe (isNothing tAuth) verify (senderKey q)) <$> get SSender Cmd SSender SEND {} -> verifyQueue (\q -> Just q `verified` maybe (isNothing tAuth) verify (senderKey q)) <$> get SSender
Cmd SSender PING -> pure $ VRVerified Nothing Cmd SSender PING -> pure $ VRVerified Nothing
Cmd SSender RFWD {} -> pure $ VRVerified Nothing
-- NSUB will not be accepted without authorization -- NSUB will not be accepted without authorization
Cmd SNotifier NSUB -> verifyQueue (\q -> maybe dummyVerify (Just q `verifiedWith`) (notifierKey <$> notifier q)) <$> get SNotifier Cmd SNotifier NSUB -> verifyQueue (\q -> maybe dummyVerify (\n -> Just q `verifiedWith` notifierKey n) (notifier q)) <$> get SNotifier
Cmd SProxiedClient _ -> pure $ VRVerified Nothing
where where
verify = verifyCmdAuthorization auth_ tAuth authorized verify = verifyCmdAuthorization auth_ tAuth authorized
dummyVerify = verify (dummyAuthKey tAuth) `seq` VRFailed dummyVerify = verify (dummyAuthKey tAuth) `seq` VRFailed
verifyQueue :: (QueueRec -> VerificationResult) -> Either ErrorType QueueRec -> VerificationResult verifyQueue :: (QueueRec -> VerificationResult) -> Either ErrorType QueueRec -> VerificationResult
verifyQueue = either (\_ -> dummyVerify) verifyQueue = either (const dummyVerify)
verified q cond = if cond then VRVerified q else VRFailed verified q cond = if cond then VRVerified q else VRFailed
verifiedWith q k = q `verified` verify k verifiedWith q k = q `verified` verify k
get :: SParty p -> M (Either ErrorType QueueRec) get :: DirectParty p => SParty p -> M (Either ErrorType QueueRec)
get party = do get party = do
st <- asks queueStore st <- asks queueStore
atomically $ getQueue st party queueId atomically $ getQueue st party queueId
verifyCmdAuthorization :: Maybe (THandleAuth, C.CbNonce) -> Maybe TransmissionAuth -> ByteString -> C.APublicAuthKey -> Bool verifyCmdAuthorization :: Maybe (THandleAuth 'TServer, C.CbNonce) -> Maybe TransmissionAuth -> ByteString -> C.APublicAuthKey -> Bool
verifyCmdAuthorization auth_ tAuth authorized key = maybe False (verify key) tAuth verifyCmdAuthorization auth_ tAuth authorized key = maybe False (verify key) tAuth
where where
verify :: C.APublicAuthKey -> TransmissionAuth -> Bool verify :: C.APublicAuthKey -> TransmissionAuth -> Bool
@@ -548,12 +569,12 @@ verifyCmdAuthorization auth_ tAuth authorized key = maybe False (verify key) tAu
C.SX25519 -> verifyCmdAuth auth_ k s authorized C.SX25519 -> verifyCmdAuth auth_ k s authorized
_ -> verifyCmdAuth auth_ dummyKeyX25519 s authorized `seq` False _ -> verifyCmdAuth auth_ dummyKeyX25519 s authorized `seq` False
verifyCmdAuth :: Maybe (THandleAuth, C.CbNonce) -> C.PublicKeyX25519 -> C.CbAuthenticator -> ByteString -> Bool verifyCmdAuth :: Maybe (THandleAuth 'TServer, C.CbNonce) -> C.PublicKeyX25519 -> C.CbAuthenticator -> ByteString -> Bool
verifyCmdAuth auth_ k authenticator authorized = case auth_ of verifyCmdAuth auth_ k authenticator authorized = case auth_ of
Just (THandleAuth {privKey}, nonce) -> C.cbVerify k privKey nonce authenticator authorized Just (THAuthServer {serverPrivKey = pk}, nonce) -> C.cbVerify k pk nonce authenticator authorized
Nothing -> False Nothing -> False
dummyVerifyCmd :: Maybe (THandleAuth, C.CbNonce) -> ByteString -> TransmissionAuth -> Bool dummyVerifyCmd :: Maybe (THandleAuth 'TServer, C.CbNonce) -> ByteString -> TransmissionAuth -> Bool
dummyVerifyCmd auth_ authorized = \case dummyVerifyCmd auth_ authorized = \case
TASignature (C.ASignature a s) -> C.verify' (dummySignKey a) s authorized TASignature (C.ASignature a s) -> C.verify' (dummySignKey a) s authorized
TAAuthenticator s -> verifyCmdAuth auth_ dummyKeyX25519 s authorized TAAuthenticator s -> verifyCmdAuth auth_ dummyKeyX25519 s authorized
@@ -581,25 +602,55 @@ dummyKeyEd448 = "MEMwBQYDK2VxAzoA6ibQc9XpkSLtwrf7PLvp81qW/etiumckVFImCMRdftcG/Xo
dummyKeyX25519 :: C.PublicKey 'C.X25519 dummyKeyX25519 :: C.PublicKey 'C.X25519
dummyKeyX25519 = "MCowBQYDK2VuAyEA4JGSMYht18H4mas/jHeBwfcM7jLwNYJNOAhi2/g4RXg=" dummyKeyX25519 = "MCowBQYDK2VuAyEA4JGSMYht18H4mas/jHeBwfcM7jLwNYJNOAhi2/g4RXg="
client :: Client -> Server -> M () client :: THandleParams SMPVersion 'TServer -> Client -> Server -> M ()
client clnt@Client {subscriptions, ntfSubscriptions, rcvQ, sndQ, sessionId} Server {subscribedQ, ntfSubscribedQ, notifiers} = do client thParams' clnt@Client {subscriptions, ntfSubscriptions, rcvQ, sndQ, sessionId} Server {subscribedQ, ntfSubscribedQ, notifiers} = do
labelMyThread . B.unpack $ "client $" <> encode sessionId <> " commands" labelMyThread . B.unpack $ "client $" <> encode sessionId <> " commands"
forever $ forever $ do
atomically (readTBQueue rcvQ) (proxied, rs) <- partitionEithers . L.toList <$> (mapM processCommand =<< atomically (readTBQueue rcvQ))
>>= mapM processCommand forM_ (L.nonEmpty rs) reply
>>= atomically . writeTBQueue sndQ -- TODO cancel this thread if the client gets disconnected
-- TODO limit client concurrency
forM_ (L.nonEmpty proxied) $ \cmds -> forkIO $ mapConcurrently processProxiedCmd cmds >>= reply
where where
processCommand :: (Maybe QueueRec, Transmission Cmd) -> M (Transmission BrokerMsg) reply :: MonadIO m => NonEmpty (Transmission BrokerMsg) -> m ()
reply = atomically . writeTBQueue sndQ
processProxiedCmd :: Transmission (Command 'ProxiedClient) -> M (Transmission BrokerMsg)
processProxiedCmd (corrId, sessId, command) = (corrId, sessId,) <$> case command of
PRXY srv auth -> ifM allowProxy getRelay (pure $ ERR AUTH)
where
allowProxy = do
ServerConfig {allowSMPProxy, newQueueBasicAuth} <- asks config
pure $ allowSMPProxy && maybe True ((== auth) . Just) newQueueBasicAuth
getRelay = do
ProxyAgent {smpAgent} <- asks proxyAgent
-- TODO catch IO errors too
liftIO $ proxyResp <$> runExceptT (getSMPServerClient' smpAgent srv)
where
proxyResp = \case
Right smp ->
let THandleParams {sessionId = srvSessId, thAuth} = thParams smp
vr = supportedServerSMPRelayVRange
in case thAuth of
Just THAuthClient {serverCertKey} -> PKEY srvSessId vr serverCertKey
Nothing -> ERR $ PROXY TRANSPORT -- TODO different error?
Left err -> ERR $ smpProxyError err
PFWD pubKey encBlock -> do
ProxyAgent {smpAgent} <- asks proxyAgent
atomically (lookupSMPServerClient smpAgent sessId) >>= \case
Just smp -> liftIO $ either (ERR . smpProxyError) PRES <$> runExceptT (forwardSMPMessage smp corrId pubKey encBlock)
Nothing -> pure $ ERR $ PROXY NO_SESSION
processCommand :: (Maybe QueueRec, Transmission Cmd) -> M (Either (Transmission (Command 'ProxiedClient)) (Transmission BrokerMsg))
processCommand (qr_, (corrId, queueId, cmd)) = do processCommand (qr_, (corrId, queueId, cmd)) = do
st <- asks queueStore st <- asks queueStore
case cmd of case cmd of
Cmd SSender command -> Cmd SProxiedClient command -> pure $ Left (corrId, queueId, command)
case command of Cmd SSender command -> Right <$> case command of
SEND flags msgBody -> withQueue $ \qr -> sendMessage qr flags msgBody SEND flags msgBody -> withQueue $ \qr -> sendMessage qr flags msgBody
PING -> pure (corrId, "", PONG) PING -> pure (corrId, "", PONG)
Cmd SNotifier NSUB -> subscribeNotifications RFWD encBlock -> (corrId, "",) <$> processForwardedCommand encBlock
Cmd SNotifier NSUB -> Right <$> subscribeNotifications
Cmd SRecipient command -> Cmd SRecipient command ->
case command of Right <$> case command of
NEW rKey dhKey auth subMode -> NEW rKey dhKey auth subMode ->
ifM ifM
allowNew allowNew
@@ -863,6 +914,59 @@ client clnt@Client {subscriptions, ntfSubscriptions, rcvQ, sndQ, sessionId} Serv
encNMsgMeta = C.cbEncrypt rcvNtfDhSecret cbNonce (smpEncode msgMeta) 128 encNMsgMeta = C.cbEncrypt rcvNtfDhSecret cbNonce (smpEncode msgMeta) 128
pure . (cbNonce,) $ fromRight "" encNMsgMeta pure . (cbNonce,) $ fromRight "" encNMsgMeta
processForwardedCommand :: EncFwdTransmission -> M BrokerMsg
processForwardedCommand (EncFwdTransmission s) = fmap (either id id) . runExceptT $ do
-- TODO error
THAuthServer {clientPeerPubKey, serverPrivKey} <- maybe (throwError $ ERR INTERNAL) pure thAuth
-- TODO compute during handshake?
let sessSecret = C.dh' clientPeerPubKey serverPrivKey
proxyNonce = C.cbNonce $ bs corrId
-- TODO error
s' <- liftEitherWith internalErr $ C.cbDecrypt sessSecret proxyNonce s
-- TODO error
FwdTransmission {fwdCorrId, fwdKey, fwdTransmission = EncTransmission et} <- liftEitherWith internalErr $ smpDecode s'
-- TODO error - this error is reported to proxy, as we failed to get to client's transmission
let clientSecret = C.dh' fwdKey serverPrivKey
clientNonce = C.cbNonce $ bs fwdCorrId
b <- liftEitherWith internalErr $ C.cbDecrypt clientSecret clientNonce et
-- only allowing single forwarded transactions
let t' = tDecodeParseValidate thParams' $ L.head $ tParse thParams' b
clntThAuth = Just $ THAuthServer {clientPeerPubKey = fwdKey, serverPrivKey}
-- TODO error
r <-
lift (rejectOrVerify clntThAuth t') >>= \case
Left r -> pure r
Right t''@(_, (corrId', entId', _)) ->
-- Left will not be returned by processCommand, as only SEND command is allowed
fromRight (corrId', entId', ERR INTERNAL) <$> lift (processCommand t'')
-- encode response
r' <- case batchTransmissions (batch thParams') (blockSize thParams') [Right (Nothing, encodeTransmission thParams' r)] of
[] -> throwE $ ERR INTERNAL -- TODO error
TBError _ _ : _ -> throwE $ ERR INTERNAL -- TODO error
TBTransmission b' _ : _ -> pure b'
TBTransmissions b' _ _ : _ -> pure b'
-- encrypt to client
r2 <- liftEitherWith internalErr $ EncResponse <$> C.cbEncrypt clientSecret (C.reverseNonce clientNonce) r' paddedProxiedMsgLength
-- encrypt to proxy
let fr = FwdResponse {fwdCorrId, fwdResponse = r2}
r3 <- liftEitherWith internalErr $ EncFwdResponse <$> C.cbEncrypt sessSecret (C.reverseNonce proxyNonce) (smpEncode fr) paddedForwardedMsgLength
pure $ RRES r3
where
internalErr _ = ERR INTERNAL -- TODO errors
THandleParams {thAuth} = thParams'
rejectOrVerify :: Maybe (THandleAuth 'TServer) -> SignedTransmission ErrorType Cmd -> M (Either (Transmission BrokerMsg) (Maybe QueueRec, Transmission Cmd))
rejectOrVerify clntThAuth (tAuth, authorized, (corrId', entId', cmdOrError)) =
case cmdOrError of
Left e -> pure $ Left (corrId', entId', ERR e)
-- flags msgBody -> withQueue $ \qr -> sendMessage qr flags msgBody
Right cmd'@(Cmd SSender SEND {}) -> verified <$> verifyTransmission ((,C.cbNonce (bs corrId')) <$> clntThAuth) tAuth authorized entId' cmd'
where
verified = \case
VRVerified qr -> Right (qr, (corrId', entId', cmd'))
VRFailed -> Left (corrId', entId', ERR AUTH)
Right _ -> pure $ Left (corrId', entId', ERR $ CMD PROHIBITED)
deliverMessage :: T.Text -> QueueRec -> RecipientId -> TVar Sub -> MsgQueue -> Maybe Message -> M (Transmission BrokerMsg) deliverMessage :: T.Text -> QueueRec -> RecipientId -> TVar Sub -> MsgQueue -> Maybe Message -> M (Transmission BrokerMsg)
deliverMessage name qr rId sub q msg_ = time (name <> " deliver") $ do deliverMessage name qr rId sub q msg_ = time (name <> " deliver") $ do
readTVarIO sub >>= \case readTVarIO sub >>= \case
+28 -10
View File
@@ -22,6 +22,7 @@ import Network.Socket (ServiceName)
import qualified Network.TLS as T import qualified Network.TLS as T
import Numeric.Natural import Numeric.Natural
import Simplex.Messaging.Agent.Lock import Simplex.Messaging.Agent.Lock
import Simplex.Messaging.Client.Agent (SMPClientAgent, SMPClientAgentConfig, newSMPClientAgent)
import Simplex.Messaging.Crypto (KeyHash (..)) import Simplex.Messaging.Crypto (KeyHash (..))
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Protocol import Simplex.Messaging.Protocol
@@ -33,7 +34,7 @@ import Simplex.Messaging.Server.Stats
import Simplex.Messaging.Server.StoreLog import Simplex.Messaging.Server.StoreLog
import Simplex.Messaging.TMap (TMap) import Simplex.Messaging.TMap (TMap)
import qualified Simplex.Messaging.TMap as TM import qualified Simplex.Messaging.TMap as TM
import Simplex.Messaging.Transport (ATransport, VersionSMP, VersionRangeSMP) import Simplex.Messaging.Transport (ATransport, VersionRangeSMP, VersionSMP)
import Simplex.Messaging.Transport.Server (SocketState, TransportServerConfig, loadFingerprint, loadTLSServerParams, newSocketState) import Simplex.Messaging.Transport.Server (SocketState, TransportServerConfig, loadFingerprint, loadTLSServerParams, newSocketState)
import System.IO (IOMode (..)) import System.IO (IOMode (..))
import System.Mem.Weak (Weak) import System.Mem.Weak (Weak)
@@ -79,7 +80,9 @@ data ServerConfig = ServerConfig
-- | TCP transport config -- | TCP transport config
transportConfig :: TransportServerConfig, transportConfig :: TransportServerConfig,
-- | run listener on control port -- | run listener on control port
controlPort :: Maybe ServiceName controlPort :: Maybe ServiceName,
smpAgentCfg :: SMPClientAgentConfig,
allowSMPProxy :: Bool -- auth is the same with `newQueueBasicAuth`
} }
defMsgExpirationDays :: Int64 defMsgExpirationDays :: Int64
@@ -110,8 +113,9 @@ data Env = Env
tlsServerParams :: T.ServerParams, tlsServerParams :: T.ServerParams,
serverStats :: ServerStats, serverStats :: ServerStats,
sockets :: SocketState, sockets :: SocketState,
clientSeq :: TVar Int, clientSeq :: TVar ClientId,
clients :: TVar (IntMap Client) clients :: TVar (IntMap Client),
proxyAgent :: ProxyAgent -- senders served on this proxy
} }
data Server = Server data Server = Server
@@ -122,8 +126,14 @@ data Server = Server
savingLock :: Lock savingLock :: Lock
} }
data ProxyAgent = ProxyAgent
{ smpAgent :: SMPClientAgent
}
type ClientId = Int
data Client = Client data Client = Client
{ clientId :: Int, { clientId :: ClientId,
subscriptions :: TMap RecipientId (TVar Sub), subscriptions :: TMap RecipientId (TVar Sub),
ntfSubscriptions :: TMap NotifierId (), ntfSubscriptions :: TMap NotifierId (),
rcvQ :: TBQueue (NonEmpty (Maybe QueueRec, Transmission Cmd)), rcvQ :: TBQueue (NonEmpty (Maybe QueueRec, Transmission Cmd)),
@@ -135,7 +145,8 @@ data Client = Client
connected :: TVar Bool, connected :: TVar Bool,
createdAt :: SystemTime, createdAt :: SystemTime,
rcvActiveAt :: TVar 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 data SubscriptionThread = NoSub | SubPending | SubThread (Weak ThreadId) | ProhibitSub
@@ -154,7 +165,7 @@ newServer = do
savingLock <- createLock savingLock <- createLock
return Server {subscribedQ, subscribers, ntfSubscribedQ, notifiers, savingLock} 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 newClient nextClientId qSize thVersion sessionId createdAt = do
clientId <- stateTVar nextClientId $ \next -> (next, next + 1) clientId <- stateTVar nextClientId $ \next -> (next, next + 1)
subscriptions <- TM.empty subscriptions <- TM.empty
@@ -166,7 +177,8 @@ newClient nextClientId qSize thVersion sessionId createdAt = do
connected <- newTVar True connected <- newTVar True
rcvActiveAt <- newTVar createdAt rcvActiveAt <- newTVar createdAt
sndActiveAt <- 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 :: SubscriptionThread -> STM Sub
newSubscription subThread = do newSubscription subThread = do
@@ -174,7 +186,7 @@ newSubscription subThread = do
return Sub {subThread, delivered} return Sub {subThread, delivered}
newEnv :: ServerConfig -> IO Env newEnv :: ServerConfig -> IO Env
newEnv config@ServerConfig {caCertificateFile, certificateFile, privateKeyFile, storeLogFile} = do newEnv config@ServerConfig {caCertificateFile, certificateFile, privateKeyFile, storeLogFile, smpAgentCfg} = do
server <- atomically newServer server <- atomically newServer
queueStore <- atomically newQueueStore queueStore <- atomically newQueueStore
msgStore <- atomically newMsgStore msgStore <- atomically newMsgStore
@@ -187,7 +199,8 @@ newEnv config@ServerConfig {caCertificateFile, certificateFile, privateKeyFile,
sockets <- atomically newSocketState sockets <- atomically newSocketState
clientSeq <- newTVarIO 0 clientSeq <- newTVarIO 0
clients <- newTVarIO mempty clients <- newTVarIO mempty
return Env {config, server, serverIdentity, queueStore, msgStore, random, storeLog, tlsServerParams, serverStats, sockets, clientSeq, clients} proxyAgent <- atomically $ newSMPProxyAgent smpAgentCfg random
return Env {config, server, serverIdentity, queueStore, msgStore, random, storeLog, tlsServerParams, serverStats, sockets, clientSeq, clients, proxyAgent}
where where
restoreQueues :: QueueStore -> FilePath -> IO (StoreLog 'WriteMode) restoreQueues :: QueueStore -> FilePath -> IO (StoreLog 'WriteMode)
restoreQueues QueueStore {queues, senders, notifiers} f = do restoreQueues QueueStore {queues, senders, notifiers} f = do
@@ -203,3 +216,8 @@ newEnv config@ServerConfig {caCertificateFile, certificateFile, privateKeyFile,
addNotifier q = case notifier q of addNotifier q = case notifier q of
Nothing -> id Nothing -> id
Just NtfCreds {notifierId} -> M.insert notifierId (recipientId q) Just NtfCreds {notifierId} -> M.insert notifierId (recipientId q)
newSMPProxyAgent :: SMPClientAgentConfig -> TVar ChaChaDRG -> STM ProxyAgent
newSMPProxyAgent smpAgentCfg random = do
smpAgent <- newSMPClientAgent smpAgentCfg random
pure ProxyAgent {smpAgent}
+7 -3
View File
@@ -18,6 +18,8 @@ import qualified Data.Text as T
import Data.Text.Encoding (encodeUtf8) import Data.Text.Encoding (encodeUtf8)
import Network.Socket (HostName) import Network.Socket (HostName)
import Options.Applicative import Options.Applicative
import Simplex.Messaging.Client (ProtocolClientConfig (..))
import Simplex.Messaging.Client.Agent (SMPClientAgentConfig (..), defaultSMPClientAgentConfig)
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Encoding.String import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Protocol (BasicAuth (..), ProtoServerWithAuth (ProtoServerWithAuth), pattern SMPServer) import Simplex.Messaging.Protocol (BasicAuth (..), ProtoServerWithAuth (ProtoServerWithAuth), pattern SMPServer)
@@ -25,10 +27,11 @@ import Simplex.Messaging.Server (runSMPServer)
import Simplex.Messaging.Server.CLI import Simplex.Messaging.Server.CLI
import Simplex.Messaging.Server.Env.STM (ServerConfig (..), defMsgExpirationDays, defaultInactiveClientExpiration, defaultMessageExpiration) import Simplex.Messaging.Server.Env.STM (ServerConfig (..), defMsgExpirationDays, defaultInactiveClientExpiration, defaultMessageExpiration)
import Simplex.Messaging.Server.Expiration import Simplex.Messaging.Server.Expiration
import Simplex.Messaging.Transport (simplexMQVersion, supportedServerSMPRelayVRange) import Simplex.Messaging.Transport (simplexMQVersion, supportedServerSMPRelayVRange, batchCmdsSMPVersion, sendingProxySMPVersion)
import Simplex.Messaging.Transport.Client (TransportHost (..)) import Simplex.Messaging.Transport.Client (TransportHost (..))
import Simplex.Messaging.Transport.Server (TransportServerConfig (..), defaultTransportServerConfig) import Simplex.Messaging.Transport.Server (TransportServerConfig (..), defaultTransportServerConfig)
import Simplex.Messaging.Util (safeDecodeUtf8) import Simplex.Messaging.Util (safeDecodeUtf8)
import Simplex.Messaging.Version (mkVersionRange)
import System.Directory (createDirectoryIfMissing, doesFileExist) import System.Directory (createDirectoryIfMissing, doesFileExist)
import System.FilePath (combine) import System.FilePath (combine)
import System.IO (BufferMode (..), hSetBuffering, stderr, stdout) import System.IO (BufferMode (..), hSetBuffering, stderr, stdout)
@@ -213,7 +216,9 @@ smpServerCLI cfgPath logPath =
defaultTransportServerConfig defaultTransportServerConfig
{ logTLSErrors = fromMaybe False $ iniOnOff "TRANSPORT" "log_tls_errors" ini { 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,
smpAgentCfg = defaultSMPClientAgentConfig {smpCfg = (smpCfg defaultSMPClientAgentConfig) {serverVRange = mkVersionRange batchCmdsSMPVersion sendingProxySMPVersion}},
allowSMPProxy = True -- TODO: "get from INI"
} }
data CliCommand data CliCommand
@@ -305,4 +310,3 @@ cliCommandP cfgPath logPath iniFile =
pure InitOptions {enableStoreLog, logStats, signAlgorithm, ip, fqdn, password, scripted} pure InitOptions {enableStoreLog, logStats, signAlgorithm, ip, fqdn, password, scripted}
parseBasicAuth :: ReadM ServerPassword parseBasicAuth :: ReadM ServerPassword
parseBasicAuth = eitherReader $ fmap ServerPassword . strDecode . B.pack parseBasicAuth = eitherReader $ fmap ServerPassword . strDecode . B.pack
@@ -54,7 +54,7 @@ addQueue QueueStore {queues, senders} q@QueueRec {recipientId = rId, senderId =
where where
hasId = (||) <$> TM.member rId queues <*> TM.member sId senders hasId = (||) <$> TM.member rId queues <*> TM.member sId senders
getQueue :: QueueStore -> SParty p -> QueueId -> STM (Either ErrorType QueueRec) getQueue :: DirectParty p => QueueStore -> SParty p -> QueueId -> STM (Either ErrorType QueueRec)
getQueue QueueStore {queues, senders, notifiers} party qId = getQueue QueueStore {queues, senders, notifiers} party qId =
toResult <$> (mapM readTVar =<< getVar) toResult <$> (mapM readTVar =<< getVar)
where where
+54 -26
View File
@@ -12,6 +12,7 @@
{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeApplications #-}
-- | -- |
@@ -40,6 +41,7 @@ module Simplex.Messaging.Transport
basicAuthSMPVersion, basicAuthSMPVersion,
subModeSMPVersion, subModeSMPVersion,
authCmdsSMPVersion, authCmdsSMPVersion,
sendingProxySMPVersion,
simplexMQVersion, simplexMQVersion,
smpBlockSize, smpBlockSize,
TransportConfig (..), TransportConfig (..),
@@ -113,6 +115,11 @@ import UnliftIO.STM
-- * Transport parameters -- * Transport parameters
-- min size it works with:
-- unsigned message: 16292 (paddedProxiedMsgLength = 16151, paddedForwardedMsgLength = 16239)
-- Ed448: 16406 (16384 + 22, fails with 21)
-- Ed25519: 16356
-- X25519: 16381
smpBlockSize :: Int smpBlockSize :: Int
smpBlockSize = 16384 smpBlockSize = 16384
@@ -148,6 +155,9 @@ subModeSMPVersion = VersionSMP 6
authCmdsSMPVersion :: VersionSMP authCmdsSMPVersion :: VersionSMP
authCmdsSMPVersion = VersionSMP 7 authCmdsSMPVersion = VersionSMP 7
sendingProxySMPVersion :: VersionSMP
sendingProxySMPVersion = VersionSMP 8
currentClientSMPRelayVersion :: VersionSMP currentClientSMPRelayVersion :: VersionSMP
currentClientSMPRelayVersion = VersionSMP 6 currentClientSMPRelayVersion = VersionSMP 6
@@ -311,20 +321,20 @@ instance Transport TLS where
-- * SMP transport -- * SMP transport
-- | The handle for SMP encrypted transport connection over Transport. -- | The handle for SMP encrypted transport connection over Transport.
data THandle v c = THandle data THandle v c p = THandle
{ connection :: c, { connection :: c,
params :: THandleParams v params :: THandleParams v p
} }
type THandleSMP c = THandle SMPVersion c type THandleSMP c p = THandle SMPVersion c p
data THandleParams v = THandleParams data THandleParams v p = THandleParams
{ sessionId :: SessionId, { sessionId :: SessionId,
blockSize :: Int, blockSize :: Int,
-- | agreed server protocol version -- | agreed server protocol version
thVersion :: Version v, thVersion :: Version v,
-- | peer public key for command authorization and shared secrets for entity ID encryption -- | peer public key for command authorization and shared secrets for entity ID encryption
thAuth :: Maybe THandleAuth, thAuth :: Maybe (THandleAuth p),
-- | do NOT send session ID in transmission, but include it into signed message -- | do NOT send session ID in transmission, but include it into signed message
-- based on protocol version -- based on protocol version
implySessId :: Bool, implySessId :: Bool,
@@ -333,10 +343,18 @@ data THandleParams v = THandleParams
batch :: Bool batch :: Bool
} }
data THandleAuth = THandleAuth data THandleAuth (p :: TransportPeer) where
{ peerPubKey :: C.PublicKeyX25519, -- used only in the client to combine with per-queue key THAuthClient ::
privKey :: C.PrivateKeyX25519 -- used to combine with peer's per-queue key (currently only in the server) { serverPeerPubKey :: C.PublicKeyX25519, -- used only in the client to combine with per-queue key
} serverCertKey :: (X.CertificateChain, X.SignedExact X.PubKey), -- the key here is clientPrivKey signed with server certificate
clientPrivKey :: C.PrivateKeyX25519 -- used to combine with peer's per-queue key (currently only in the server)
} ->
THandleAuth 'TClient
THAuthServer ::
{ clientPeerPubKey :: C.PublicKeyX25519, -- used only in the client to combine with per-queue key
serverPrivKey :: C.PrivateKeyX25519 -- used to combine with peer's per-queue key (currently only in the server)
} ->
THandleAuth 'TServer
-- | TLS-unique channel binding -- | TLS-unique channel binding
type SessionId = ByteString type SessionId = ByteString
@@ -345,6 +363,7 @@ data ServerHandshake = ServerHandshake
{ smpVersionRange :: VersionRangeSMP, { smpVersionRange :: VersionRangeSMP,
sessionId :: SessionId, sessionId :: SessionId,
-- pub key to agree shared secrets for command authorization and entity ID encryption. -- pub key to agree shared secrets for command authorization and entity ID encryption.
-- todo C.PublicKeyX25519
authPubKey :: Maybe (X.CertificateChain, X.SignedExact X.PubKey) authPubKey :: Maybe (X.CertificateChain, X.SignedExact X.PubKey)
} }
@@ -438,13 +457,13 @@ serializeTransportError = \case
TEHandshake e -> "HANDSHAKE " <> bshow e TEHandshake e -> "HANDSHAKE " <> bshow e
-- | Pad and send block to SMP transport. -- | Pad and send block to SMP transport.
tPutBlock :: Transport c => THandle v c -> ByteString -> IO (Either TransportError ()) tPutBlock :: Transport c => THandle v c p -> ByteString -> IO (Either TransportError ())
tPutBlock THandle {connection = c, params = THandleParams {blockSize}} block = tPutBlock THandle {connection = c, params = THandleParams {blockSize}} block =
bimapM (const $ pure TELargeMsg) (cPut c) $ bimapM (const $ pure TELargeMsg) (cPut c) $
C.pad block blockSize C.pad block blockSize
-- | Receive block from SMP transport. -- | Receive block from SMP transport.
tGetBlock :: Transport c => THandle v c -> IO (Either TransportError ByteString) tGetBlock :: Transport c => THandle v c p -> IO (Either TransportError ByteString)
tGetBlock THandle {connection = c, params = THandleParams {blockSize}} = do tGetBlock THandle {connection = c, params = THandleParams {blockSize}} = do
msg <- cGet c blockSize msg <- cGet c blockSize
if B.length msg == blockSize if B.length msg == blockSize
@@ -454,7 +473,7 @@ tGetBlock THandle {connection = c, params = THandleParams {blockSize}} = do
-- | Server SMP transport handshake. -- | Server SMP transport handshake.
-- --
-- See https://github.com/simplex-chat/simplexmq/blob/master/protocol/simplex-messaging.md#appendix-a -- See https://github.com/simplex-chat/simplexmq/blob/master/protocol/simplex-messaging.md#appendix-a
smpServerHandshake :: forall c. Transport c => C.APrivateSignKey -> c -> C.KeyPairX25519 -> C.KeyHash -> VersionRangeSMP -> ExceptT TransportError IO (THandleSMP c) smpServerHandshake :: forall c. Transport c => C.APrivateSignKey -> c -> C.KeyPairX25519 -> C.KeyHash -> VersionRangeSMP -> ExceptT TransportError IO (THandleSMP c 'TServer)
smpServerHandshake serverSignKey c (k, pk) kh smpVRange = do smpServerHandshake serverSignKey c (k, pk) kh smpVRange = do
let th@THandle {params = THandleParams {sessionId}} = smpTHandle c let th@THandle {params = THandleParams {sessionId}} = smpTHandle c
sk = C.signX509 serverSignKey $ C.publicToX509 k sk = C.signX509 serverSignKey $ C.publicToX509 k
@@ -465,13 +484,13 @@ smpServerHandshake serverSignKey c (k, pk) kh smpVRange = do
| keyHash /= kh -> | keyHash /= kh ->
throwE $ TEHandshake IDENTITY throwE $ TEHandshake IDENTITY
| v `isCompatible` smpVRange -> | v `isCompatible` smpVRange ->
pure $ smpThHandle th v pk k' pure $ smpThHandleServer th v pk k'
| otherwise -> throwE $ TEHandshake VERSION | otherwise -> throwE $ TEHandshake VERSION
-- | Client SMP transport handshake. -- | Client SMP transport handshake.
-- --
-- See https://github.com/simplex-chat/simplexmq/blob/master/protocol/simplex-messaging.md#appendix-a -- See https://github.com/simplex-chat/simplexmq/blob/master/protocol/simplex-messaging.md#appendix-a
smpClientHandshake :: forall c. Transport c => c -> C.KeyPairX25519 -> C.KeyHash -> VersionRangeSMP -> ExceptT TransportError IO (THandleSMP c) smpClientHandshake :: forall c. Transport c => c -> C.KeyPairX25519 -> C.KeyHash -> VersionRangeSMP -> ExceptT TransportError IO (THandleSMP c 'TClient)
smpClientHandshake c (k, pk) keyHash@(C.KeyHash kh) smpVRange = do smpClientHandshake c (k, pk) keyHash@(C.KeyHash kh) smpVRange = do
let th@THandle {params = THandleParams {sessionId}} = smpTHandle c let th@THandle {params = THandleParams {sessionId}} = smpTHandle c
ServerHandshake {sessionId = sessId, smpVersionRange, authPubKey} <- getHandshake th ServerHandshake {sessionId = sessId, smpVersionRange, authPubKey} <- getHandshake th
@@ -479,33 +498,42 @@ smpClientHandshake c (k, pk) keyHash@(C.KeyHash kh) smpVRange = do
then throwE TEBadSession then throwE TEBadSession
else case smpVersionRange `compatibleVersion` smpVRange of else case smpVersionRange `compatibleVersion` smpVRange of
Just (Compatible v) -> do Just (Compatible v) -> do
sk_ <- forM authPubKey $ \(X.CertificateChain cert, exact) -> ck_ <- forM authPubKey $ \certKey@(X.CertificateChain cert, exact) ->
liftEitherWith (const $ TEHandshake BAD_AUTH) $ do liftEitherWith (const $ TEHandshake BAD_AUTH) $ do
case cert of case cert of
[_leaf, ca] | XV.Fingerprint kh == XV.getFingerprint ca X.HashSHA256 -> pure () [_leaf, ca] | XV.Fingerprint kh == XV.getFingerprint ca X.HashSHA256 -> pure ()
_ -> throwError "bad certificate" _ -> throwError "bad certificate"
serverKey <- getServerVerifyKey c serverKey <- getServerVerifyKey c
pubKey <- C.verifyX509 serverKey exact pubKey <- C.verifyX509 serverKey exact
C.x509ToPublic (pubKey, []) >>= C.pubKey (,certKey) <$> (C.x509ToPublic (pubKey, []) >>= C.pubKey)
sendHandshake th $ ClientHandshake {smpVersion = v, keyHash, authPubKey = Just k} sendHandshake th $ ClientHandshake {smpVersion = v, keyHash, authPubKey = Just k}
pure $ smpThHandle th v pk sk_ pure $ smpThHandleClient th v pk ck_
Nothing -> throwE $ TEHandshake VERSION Nothing -> throwE $ TEHandshake VERSION
smpThHandle :: forall c. THandleSMP c -> VersionSMP -> C.PrivateKeyX25519 -> Maybe C.PublicKeyX25519 -> THandleSMP c smpThHandleServer :: forall c. THandleSMP c 'TServer -> VersionSMP -> C.PrivateKeyX25519 -> Maybe C.PublicKeyX25519 -> THandleSMP c 'TServer
smpThHandle th@THandle {params} v privKey k_ = smpThHandleServer th v pk k_ =
-- TODO drop SMP v6: make thAuth non-optional let thAuth = (\k -> THAuthServer {clientPeerPubKey = k, serverPrivKey = pk}) <$> k_
let thAuth = (\k -> THandleAuth {peerPubKey = k, privKey}) <$> k_ in smpThHandle_ th v thAuth
params' = params {thVersion = v, thAuth, implySessId = v >= authCmdsSMPVersion}
in (th :: THandleSMP c) {params = params'}
sendHandshake :: (Transport c, Encoding smp) => THandle v c -> smp -> ExceptT TransportError IO () smpThHandleClient :: forall c. THandleSMP c 'TClient -> VersionSMP -> C.PrivateKeyX25519 -> Maybe (C.PublicKeyX25519, (X.CertificateChain, X.SignedExact X.PubKey)) -> THandleSMP c 'TClient
smpThHandleClient th v pk ck_ =
let thAuth = (\(k, ck) -> THAuthClient {serverPeerPubKey = k, serverCertKey = ck, clientPrivKey = pk}) <$> ck_
in smpThHandle_ th v thAuth
smpThHandle_ :: forall c p. THandleSMP c p -> VersionSMP -> Maybe (THandleAuth p) -> THandleSMP c p
smpThHandle_ th@THandle {params} v thAuth =
-- TODO drop SMP v6: make thAuth non-optional
let params' = params {thVersion = v, thAuth, implySessId = v >= authCmdsSMPVersion}
in (th :: THandleSMP c p) {params = params'}
sendHandshake :: (Transport c, Encoding smp) => THandle v c p -> smp -> ExceptT TransportError IO ()
sendHandshake th = ExceptT . tPutBlock th . smpEncode sendHandshake th = ExceptT . tPutBlock th . smpEncode
-- ignores tail bytes to allow future extensions -- ignores tail bytes to allow future extensions
getHandshake :: (Transport c, Encoding smp) => THandle v c -> ExceptT TransportError IO smp getHandshake :: (Transport c, Encoding smp) => THandle v c p -> ExceptT TransportError IO smp
getHandshake th = ExceptT $ (first (\_ -> TEHandshake PARSE) . A.parseOnly smpP =<<) <$> tGetBlock th getHandshake th = ExceptT $ (first (\_ -> TEHandshake PARSE) . A.parseOnly smpP =<<) <$> tGetBlock th
smpTHandle :: Transport c => c -> THandleSMP c smpTHandle :: Transport c => c -> THandleSMP c p
smpTHandle c = THandle {connection = c, params} smpTHandle c = THandle {connection = c, params}
where where
params = THandleParams {sessionId = tlsUnique c, blockSize = smpBlockSize, thVersion = VersionSMP 0, thAuth = Nothing, implySessId = False, batch = True} params = THandleParams {sessionId = tlsUnique c, blockSize = smpBlockSize, thVersion = VersionSMP 0, thAuth = Nothing, implySessId = False, batch = True}
+22 -11
View File
@@ -1,7 +1,9 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-} {-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-} {-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE TupleSections #-} {-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
module CoreTests.BatchingTests (batchingTests) where module CoreTests.BatchingTests (batchingTests) where
@@ -11,6 +13,9 @@ import Crypto.Random (ChaChaDRG)
import qualified Data.ByteString as B import qualified Data.ByteString as B
import Data.ByteString.Char8 (ByteString) import Data.ByteString.Char8 (ByteString)
import qualified Data.List.NonEmpty as L import qualified Data.List.NonEmpty as L
import qualified Data.X509 as X
import qualified Data.X509.CertificateStore as XS
import qualified Data.X509.File as XF
import Simplex.Messaging.Client import Simplex.Messaging.Client
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Protocol import Simplex.Messaging.Protocol
@@ -276,12 +281,12 @@ randomSUB_ :: (C.AlgorithmI a, C.AuthAlgorithm a) => C.SAlgorithm a -> VersionSM
randomSUB_ a v sessId = do randomSUB_ a v sessId = do
g <- C.newRandom g <- C.newRandom
rId <- atomically $ C.randomBytes 24 g rId <- atomically $ C.randomBytes 24 g
corrId <- atomically $ CorrId <$> C.randomBytes 24 g nonce@(C.CbNonce corrId) <- atomically $ C.randomCbNonce g
(rKey, rpKey) <- atomically $ C.generateAuthKeyPair a g (rKey, rpKey) <- atomically $ C.generateAuthKeyPair a g
thAuth_ <- testTHandleAuth v g rKey thAuth_ <- testTHandleAuth v g rKey
let thParams = testTHandleParams v sessId let thParams = testTHandleParams v sessId
TransmissionForAuth {tForAuth, tToSend} = encodeTransmissionForAuth thParams (corrId, rId, Cmd SRecipient SUB) TransmissionForAuth {tForAuth, tToSend} = encodeTransmissionForAuth thParams (CorrId corrId, rId, Cmd SRecipient SUB)
pure $ (,tToSend) <$> authTransmission thAuth_ (Just rpKey) corrId tForAuth pure $ (,tToSend) <$> authTransmission thAuth_ (Just rpKey) nonce tForAuth
randomSUBCmd :: ProtocolClient SMPVersion ErrorType BrokerMsg -> IO (PCTransmission ErrorType BrokerMsg) randomSUBCmd :: ProtocolClient SMPVersion ErrorType BrokerMsg -> IO (PCTransmission ErrorType BrokerMsg)
randomSUBCmd = randomSUBCmd_ C.SEd25519 randomSUBCmd = randomSUBCmd_ C.SEd25519
@@ -306,15 +311,15 @@ randomSEND_ :: (C.AlgorithmI a, C.AuthAlgorithm a) => C.SAlgorithm a -> VersionS
randomSEND_ a v sessId len = do randomSEND_ a v sessId len = do
g <- C.newRandom g <- C.newRandom
sId <- atomically $ C.randomBytes 24 g sId <- atomically $ C.randomBytes 24 g
corrId <- atomically $ CorrId <$> C.randomBytes 3 g nonce@(C.CbNonce corrId) <- atomically $ C.randomCbNonce g
(sKey, spKey) <- atomically $ C.generateAuthKeyPair a g (sKey, spKey) <- atomically $ C.generateAuthKeyPair a g
thAuth_ <- testTHandleAuth v g sKey thAuth_ <- testTHandleAuth v g sKey
msg <- atomically $ C.randomBytes len g msg <- atomically $ C.randomBytes len g
let thParams = testTHandleParams v sessId let thParams = testTHandleParams v sessId
TransmissionForAuth {tForAuth, tToSend} = encodeTransmissionForAuth thParams (corrId, sId, Cmd SSender $ SEND noMsgFlags msg) TransmissionForAuth {tForAuth, tToSend} = encodeTransmissionForAuth thParams (CorrId corrId, sId, Cmd SSender $ SEND noMsgFlags msg)
pure $ (,tToSend) <$> authTransmission thAuth_ (Just spKey) corrId tForAuth pure $ (,tToSend) <$> authTransmission thAuth_ (Just spKey) nonce tForAuth
testTHandleParams :: VersionSMP -> ByteString -> THandleParams SMPVersion testTHandleParams :: VersionSMP -> ByteString -> THandleParams SMPVersion 'TClient
testTHandleParams v sessionId = testTHandleParams v sessionId =
THandleParams THandleParams
{ sessionId, { sessionId,
@@ -325,11 +330,17 @@ testTHandleParams v sessionId =
batch = True batch = True
} }
testTHandleAuth :: VersionSMP -> TVar ChaChaDRG -> C.APublicAuthKey -> IO (Maybe THandleAuth) testTHandleAuth :: VersionSMP -> TVar ChaChaDRG -> C.APublicAuthKey -> IO (Maybe (THandleAuth 'TClient))
testTHandleAuth v g (C.APublicAuthKey a k) = case a of testTHandleAuth v g (C.APublicAuthKey a serverPeerPubKey) = case a of
C.SX25519 | v >= authCmdsSMPVersion -> do C.SX25519 | v >= authCmdsSMPVersion -> do
(_, privKey) <- atomically $ C.generateKeyPair g (_, clientPrivKey) <- atomically $ C.generateKeyPair @'C.X25519 g
pure $ Just THandleAuth {peerPubKey = k, privKey} ca <- head <$> XS.readCertificates "tests/fixtures/ca.crt"
serverCert <- head <$> XS.readCertificates "tests/fixtures/server.crt"
serverKey <- head <$> XF.readKeyFile "tests/fixtures/server.key"
signKey <- either error pure $ C.x509ToPrivate (serverKey, []) >>= C.privKey @C.APrivateSignKey
(serverAuthPub, _) <- atomically $ C.generateKeyPair @'C.X25519 g
let serverCertKey = (X.CertificateChain [serverCert, ca], C.signX509 signKey $ C.toPubKey C.publicToX509 serverAuthPub)
pure $ Just THAuthClient {serverPeerPubKey, serverCertKey, clientPrivKey}
_ -> pure Nothing _ -> pure Nothing
randomSENDCmd :: ProtocolClient SMPVersion ErrorType BrokerMsg -> Int -> IO (PCTransmission ErrorType BrokerMsg) randomSENDCmd :: ProtocolClient SMPVersion ErrorType BrokerMsg -> Int -> IO (PCTransmission ErrorType BrokerMsg)
+7 -3
View File
@@ -2,7 +2,6 @@
{-# LANGUAGE LambdaCase #-} {-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeApplications #-}
{-# OPTIONS_GHC -Wno-orphans #-} {-# OPTIONS_GHC -Wno-orphans #-}
module CoreTests.ProtocolErrorTests where module CoreTests.ProtocolErrorTests where
@@ -14,9 +13,10 @@ import GHC.Generics (Generic)
import Generic.Random (genericArbitraryU) import Generic.Random (genericArbitraryU)
import Simplex.FileTransfer.Transport (XFTPErrorType (..)) import Simplex.FileTransfer.Transport (XFTPErrorType (..))
import Simplex.Messaging.Agent.Protocol import Simplex.Messaging.Agent.Protocol
import qualified Simplex.Messaging.Agent.Protocol as Agent
import Simplex.Messaging.Encoding import Simplex.Messaging.Encoding
import Simplex.Messaging.Encoding.String import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Protocol (CommandError (..), ErrorType (..)) import Simplex.Messaging.Protocol (CommandError (..), ErrorType (..), ProxyError (..))
import Simplex.Messaging.Transport (HandshakeError (..), TransportError (..)) import Simplex.Messaging.Transport (HandshakeError (..), TransportError (..))
import Simplex.RemoteControl.Types (RCErrorType (..)) import Simplex.RemoteControl.Types (RCErrorType (..))
import Test.Hspec import Test.Hspec
@@ -33,7 +33,7 @@ protocolErrorTests = modifyMaxSuccess (const 1000) $ do
|| strDecode (strEncode err) == Right err || strDecode (strEncode err) == Right err
where where
errHasSpaces = \case errHasSpaces = \case
BROKER srv (RESPONSE e) -> hasSpaces srv || hasSpaces e BROKER srv (Agent.RESPONSE e) -> hasSpaces srv || hasSpaces e
BROKER srv _ -> hasSpaces srv BROKER srv _ -> hasSpaces srv
_ -> False _ -> False
hasSpaces s = ' ' `B.elem` encodeUtf8 (T.pack s) hasSpaces s = ' ' `B.elem` encodeUtf8 (T.pack s)
@@ -54,6 +54,8 @@ deriving instance Generic ErrorType
deriving instance Generic CommandError deriving instance Generic CommandError
deriving instance Generic ProxyError
deriving instance Generic TransportError deriving instance Generic TransportError
deriving instance Generic HandshakeError deriving instance Generic HandshakeError
@@ -78,6 +80,8 @@ instance Arbitrary ErrorType where arbitrary = genericArbitraryU
instance Arbitrary CommandError where arbitrary = genericArbitraryU instance Arbitrary CommandError where arbitrary = genericArbitraryU
instance Arbitrary ProxyError where arbitrary = genericArbitraryU
instance Arbitrary TransportError where arbitrary = genericArbitraryU instance Arbitrary TransportError where arbitrary = genericArbitraryU
instance Arbitrary HandshakeError where arbitrary = genericArbitraryU instance Arbitrary HandshakeError where arbitrary = genericArbitraryU
+4 -4
View File
@@ -70,7 +70,7 @@ testKeyHash = "LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI="
ntfTestStoreLogFile :: FilePath ntfTestStoreLogFile :: FilePath
ntfTestStoreLogFile = "tests/tmp/ntf-server-store.log" ntfTestStoreLogFile = "tests/tmp/ntf-server-store.log"
testNtfClient :: Transport c => (THandleNTF c -> IO a) -> IO a testNtfClient :: Transport c => (THandleNTF c 'TClient -> IO a) -> IO a
testNtfClient client = do testNtfClient client = do
Right host <- pure $ chooseTransportHost defaultNetworkConfig testHost Right host <- pure $ chooseTransportHost defaultNetworkConfig testHost
runTransportClient defaultTransportClientConfig Nothing host ntfTestPort (Just testKeyHash) $ \h -> do runTransportClient defaultTransportClientConfig Nothing host ntfTestPort (Just testKeyHash) $ \h -> do
@@ -139,7 +139,7 @@ withNtfServerOn t port' = withNtfServerThreadOn t port' . const
withNtfServer :: ATransport -> IO a -> IO a withNtfServer :: ATransport -> IO a -> IO a
withNtfServer t = withNtfServerOn t ntfTestPort withNtfServer t = withNtfServerOn t ntfTestPort
runNtfTest :: forall c a. Transport c => (THandleNTF c -> IO a) -> IO a runNtfTest :: forall c a. Transport c => (THandleNTF c 'TClient -> IO a) -> IO a
runNtfTest test = withNtfServer (transport @c) $ testNtfClient test runNtfTest test = withNtfServer (transport @c) $ testNtfClient test
ntfServerTest :: ntfServerTest ::
@@ -150,7 +150,7 @@ ntfServerTest ::
IO (Maybe TransmissionAuth, ByteString, ByteString, NtfResponse) IO (Maybe TransmissionAuth, ByteString, ByteString, NtfResponse)
ntfServerTest _ t = runNtfTest $ \h -> tPut' h t >> tGet' h ntfServerTest _ t = runNtfTest $ \h -> tPut' h t >> tGet' h
where where
tPut' :: THandleNTF c -> (Maybe TransmissionAuth, ByteString, ByteString, smp) -> IO () tPut' :: THandleNTF c 'TClient -> (Maybe TransmissionAuth, ByteString, ByteString, smp) -> IO ()
tPut' h@THandle {params = THandleParams {sessionId, implySessId}} (sig, corrId, queueId, smp) = do tPut' h@THandle {params = THandleParams {sessionId, implySessId}} (sig, corrId, queueId, smp) = do
let t' = if implySessId then smpEncode (corrId, queueId, smp) else smpEncode (sessionId, corrId, queueId, smp) let t' = if implySessId then smpEncode (corrId, queueId, smp) else smpEncode (sessionId, corrId, queueId, smp)
[Right ()] <- tPut h [Right (sig, t')] [Right ()] <- tPut h [Right (sig, t')]
@@ -159,7 +159,7 @@ ntfServerTest _ t = runNtfTest $ \h -> tPut' h t >> tGet' h
[(Nothing, _, (CorrId corrId, qId, Right cmd))] <- tGet h [(Nothing, _, (CorrId corrId, qId, Right cmd))] <- tGet h
pure (Nothing, corrId, qId, cmd) pure (Nothing, corrId, qId, cmd)
ntfTest :: Transport c => TProxy c -> (THandleNTF c -> IO ()) -> Expectation ntfTest :: Transport c => TProxy c -> (THandleNTF c 'TClient -> IO ()) -> Expectation
ntfTest _ test' = runNtfTest test' `shouldReturn` () ntfTest _ test' = runNtfTest test' `shouldReturn` ()
data APNSMockRequest = APNSMockRequest data APNSMockRequest = APNSMockRequest
+3 -3
View File
@@ -6,8 +6,8 @@
{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE StandaloneDeriving #-}
{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-}
{-# OPTIONS_GHC -Wno-orphans #-} {-# OPTIONS_GHC -Wno-orphans #-}
{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-}
module NtfServerTests where module NtfServerTests where
@@ -72,13 +72,13 @@ pattern RespNtf corrId queueId command <- (_, _, (corrId, queueId, Right command
deriving instance Eq NtfResponse deriving instance Eq NtfResponse
sendRecvNtf :: forall c e. (Transport c, NtfEntityI e) => THandleNTF c -> (Maybe TransmissionAuth, ByteString, ByteString, NtfCommand e) -> IO (SignedTransmission ErrorType NtfResponse) sendRecvNtf :: forall c e. (Transport c, NtfEntityI e) => THandleNTF c 'TClient -> (Maybe TransmissionAuth, ByteString, ByteString, NtfCommand e) -> IO (SignedTransmission ErrorType NtfResponse)
sendRecvNtf h@THandle {params} (sgn, corrId, qId, cmd) = do sendRecvNtf h@THandle {params} (sgn, corrId, qId, cmd) = do
let TransmissionForAuth {tToSend} = encodeTransmissionForAuth params (CorrId corrId, qId, cmd) let TransmissionForAuth {tToSend} = encodeTransmissionForAuth params (CorrId corrId, qId, cmd)
Right () <- tPut1 h (sgn, tToSend) Right () <- tPut1 h (sgn, tToSend)
tGet1 h tGet1 h
signSendRecvNtf :: forall c e. (Transport c, NtfEntityI e) => THandleNTF c -> C.APrivateAuthKey -> (ByteString, ByteString, NtfCommand e) -> IO (SignedTransmission ErrorType NtfResponse) signSendRecvNtf :: forall c e. (Transport c, NtfEntityI e) => THandleNTF c 'TClient -> C.APrivateAuthKey -> (ByteString, ByteString, NtfCommand e) -> IO (SignedTransmission ErrorType NtfResponse)
signSendRecvNtf h@THandle {params} (C.APrivateAuthKey a pk) (corrId, qId, cmd) = do signSendRecvNtf h@THandle {params} (C.APrivateAuthKey a pk) (corrId, qId, cmd) = do
let TransmissionForAuth {tForAuth, tToSend} = encodeTransmissionForAuth params (CorrId corrId, qId, cmd) let TransmissionForAuth {tForAuth, tToSend} = encodeTransmissionForAuth params (CorrId corrId, qId, cmd)
Right () <- tPut1 h (authorize tForAuth, tToSend) Right () <- tPut1 h (authorize tForAuth, tToSend)
+34 -19
View File
@@ -16,7 +16,8 @@ import Control.Monad.Except (runExceptT)
import Data.ByteString.Char8 (ByteString) import Data.ByteString.Char8 (ByteString)
import Data.List.NonEmpty (NonEmpty) import Data.List.NonEmpty (NonEmpty)
import Network.Socket import Network.Socket
import Simplex.Messaging.Client (chooseTransportHost, defaultNetworkConfig) import Simplex.Messaging.Client (ProtocolClientConfig (..), chooseTransportHost, defaultNetworkConfig)
import Simplex.Messaging.Client.Agent (SMPClientAgentConfig (..), defaultSMPClientAgentConfig)
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Encoding import Simplex.Messaging.Encoding
import Simplex.Messaging.Protocol import Simplex.Messaging.Protocol
@@ -67,13 +68,17 @@ xit'' d t = do
ci <- runIO $ lookupEnv "CI" ci <- runIO $ lookupEnv "CI"
(if ci == Just "true" then skip "skipped on CI" . it d else it d) t (if ci == Just "true" then skip "skipped on CI" . it d else it d) t
testSMPClient :: Transport c => (THandleSMP c -> IO a) -> IO a testSMPClient :: Transport c => (THandleSMP c 'TClient -> IO a) -> IO a
testSMPClient = testSMPClientVR supportedClientSMPRelayVRange testSMPClient = testSMPClientVR supportedClientSMPRelayVRange
testSMPClientVR :: Transport c => VersionRangeSMP -> (THandleSMP c -> IO a) -> IO a testSMPClientVR :: Transport c => VersionRangeSMP -> (THandleSMP c 'TClient -> IO a) -> IO a
testSMPClientVR vr client = do testSMPClientVR vr client = do
Right useHost <- pure $ chooseTransportHost defaultNetworkConfig testHost 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 'TClient -> IO a) -> IO a
testSMPClient_ host port vr client = do
runTransportClient defaultTransportClientConfig Nothing host port (Just testKeyHash) $ \h -> do
g <- C.newRandom g <- C.newRandom
ks <- atomically $ C.generateKeyPair g ks <- atomically $ C.generateKeyPair g
runExceptT (smpClientHandshake h ks testKeyHash vr) >>= \case runExceptT (smpClientHandshake h ks testKeyHash vr) >>= \case
@@ -107,12 +112,22 @@ cfg =
certificateFile = "tests/fixtures/server.crt", certificateFile = "tests/fixtures/server.crt",
smpServerVRange = supportedServerSMPRelayVRange, smpServerVRange = supportedServerSMPRelayVRange,
transportConfig = defaultTransportServerConfig, transportConfig = defaultTransportServerConfig,
controlPort = Nothing controlPort = Nothing,
smpAgentCfg = defaultSMPClientAgentConfig,
allowSMPProxy = False
} }
cfgV7 :: ServerConfig cfgV7 :: ServerConfig
cfgV7 = cfg {smpServerVRange = mkVersionRange batchCmdsSMPVersion authCmdsSMPVersion} cfgV7 = cfg {smpServerVRange = mkVersionRange batchCmdsSMPVersion authCmdsSMPVersion}
proxyCfg :: ServerConfig
proxyCfg =
cfgV7
{ allowSMPProxy = True,
smpServerVRange = mkVersionRange batchCmdsSMPVersion sendingProxySMPVersion,
smpAgentCfg = defaultSMPClientAgentConfig {smpCfg = (smpCfg defaultSMPClientAgentConfig) {serverVRange = mkVersionRange batchCmdsSMPVersion sendingProxySMPVersion}}
}
withSmpServerStoreMsgLogOn :: HasCallStack => ATransport -> ServiceName -> (HasCallStack => ThreadId -> IO a) -> IO a withSmpServerStoreMsgLogOn :: HasCallStack => ATransport -> ServiceName -> (HasCallStack => ThreadId -> IO a) -> IO a
withSmpServerStoreMsgLogOn t = withSmpServerConfigOn t cfg {storeLogFile = Just testStoreLogFile, storeMsgsFile = Just testStoreMsgsFile, serverStatsBackupFile = Just testServerStatsBackupFile} withSmpServerStoreMsgLogOn t = withSmpServerConfigOn t cfg {storeLogFile = Just testStoreLogFile, storeMsgsFile = Just testStoreMsgsFile, serverStatsBackupFile = Just testServerStatsBackupFile}
@@ -150,16 +165,16 @@ withSmpServer t = withSmpServerOn t testPort
withSmpServerV7 :: HasCallStack => ATransport -> IO a -> IO a withSmpServerV7 :: HasCallStack => ATransport -> IO a -> IO a
withSmpServerV7 t = withSmpServerConfigOn t cfgV7 testPort . const withSmpServerV7 t = withSmpServerConfigOn t cfgV7 testPort . const
runSmpTest :: forall c a. (HasCallStack, Transport c) => (HasCallStack => THandleSMP c -> IO a) -> IO a runSmpTest :: forall c a. (HasCallStack, Transport c) => (HasCallStack => THandleSMP c 'TClient -> IO a) -> IO a
runSmpTest test = withSmpServer (transport @c) $ testSMPClient test runSmpTest test = withSmpServer (transport @c) $ testSMPClient test
runSmpTestN :: forall c a. (HasCallStack, Transport c) => Int -> (HasCallStack => [THandleSMP c] -> IO a) -> IO a runSmpTestN :: forall c a. (HasCallStack, Transport c) => Int -> (HasCallStack => [THandleSMP c 'TClient] -> IO a) -> IO a
runSmpTestN = runSmpTestNCfg cfg supportedClientSMPRelayVRange runSmpTestN = runSmpTestNCfg cfg supportedClientSMPRelayVRange
runSmpTestNCfg :: forall c a. (HasCallStack, Transport c) => ServerConfig -> VersionRangeSMP -> Int -> (HasCallStack => [THandleSMP c] -> IO a) -> IO a runSmpTestNCfg :: forall c a. (HasCallStack, Transport c) => ServerConfig -> VersionRangeSMP -> Int -> (HasCallStack => [THandleSMP c 'TClient] -> IO a) -> IO a
runSmpTestNCfg srvCfg clntVR nClients test = withSmpServerConfigOn (transport @c) srvCfg testPort $ \_ -> run nClients [] runSmpTestNCfg srvCfg clntVR nClients test = withSmpServerConfigOn (transport @c) srvCfg testPort $ \_ -> run nClients []
where where
run :: Int -> [THandleSMP c] -> IO a run :: Int -> [THandleSMP c 'TClient] -> IO a
run 0 hs = test hs run 0 hs = test hs
run n hs = testSMPClientVR clntVR $ \h -> run (n - 1) (h : hs) run n hs = testSMPClientVR clntVR $ \h -> run (n - 1) (h : hs)
@@ -171,7 +186,7 @@ smpServerTest ::
IO (Maybe TransmissionAuth, ByteString, ByteString, BrokerMsg) IO (Maybe TransmissionAuth, ByteString, ByteString, BrokerMsg)
smpServerTest _ t = runSmpTest $ \h -> tPut' h t >> tGet' h smpServerTest _ t = runSmpTest $ \h -> tPut' h t >> tGet' h
where where
tPut' :: THandleSMP c -> (Maybe TransmissionAuth, ByteString, ByteString, smp) -> IO () tPut' :: THandleSMP c 'TClient -> (Maybe TransmissionAuth, ByteString, ByteString, smp) -> IO ()
tPut' h@THandle {params = THandleParams {sessionId, implySessId}} (sig, corrId, queueId, smp) = do tPut' h@THandle {params = THandleParams {sessionId, implySessId}} (sig, corrId, queueId, smp) = do
let t' = if implySessId then smpEncode (corrId, queueId, smp) else smpEncode (sessionId, corrId, queueId, smp) let t' = if implySessId then smpEncode (corrId, queueId, smp) else smpEncode (sessionId, corrId, queueId, smp)
[Right ()] <- tPut h [Right (sig, t')] [Right ()] <- tPut h [Right (sig, t')]
@@ -180,33 +195,33 @@ smpServerTest _ t = runSmpTest $ \h -> tPut' h t >> tGet' h
[(Nothing, _, (CorrId corrId, qId, Right cmd))] <- tGet h [(Nothing, _, (CorrId corrId, qId, Right cmd))] <- tGet h
pure (Nothing, corrId, qId, cmd) pure (Nothing, corrId, qId, cmd)
smpTest :: (HasCallStack, Transport c) => TProxy c -> (HasCallStack => THandleSMP c -> IO ()) -> Expectation smpTest :: (HasCallStack, Transport c) => TProxy c -> (HasCallStack => THandleSMP c 'TClient -> IO ()) -> Expectation
smpTest _ test' = runSmpTest test' `shouldReturn` () smpTest _ test' = runSmpTest test' `shouldReturn` ()
smpTestN :: (HasCallStack, Transport c) => Int -> (HasCallStack => [THandleSMP c] -> IO ()) -> Expectation smpTestN :: (HasCallStack, Transport c) => Int -> (HasCallStack => [THandleSMP c 'TClient] -> IO ()) -> Expectation
smpTestN n test' = runSmpTestN n test' `shouldReturn` () smpTestN n test' = runSmpTestN n test' `shouldReturn` ()
smpTest2 :: forall c. (HasCallStack, Transport c) => TProxy c -> (HasCallStack => THandleSMP c -> THandleSMP c -> IO ()) -> Expectation smpTest2 :: forall c. (HasCallStack, Transport c) => TProxy c -> (HasCallStack => THandleSMP c 'TClient -> THandleSMP c 'TClient -> IO ()) -> Expectation
smpTest2 = smpTest2Cfg cfg supportedClientSMPRelayVRange smpTest2 = smpTest2Cfg cfg supportedClientSMPRelayVRange
smpTest2Cfg :: forall c. (HasCallStack, Transport c) => ServerConfig -> VersionRangeSMP -> TProxy c -> (HasCallStack => THandleSMP c -> THandleSMP c -> IO ()) -> Expectation smpTest2Cfg :: forall c. (HasCallStack, Transport c) => ServerConfig -> VersionRangeSMP -> TProxy c -> (HasCallStack => THandleSMP c 'TClient -> THandleSMP c 'TClient -> IO ()) -> Expectation
smpTest2Cfg srvCfg clntVR _ test' = runSmpTestNCfg srvCfg clntVR 2 _test `shouldReturn` () smpTest2Cfg srvCfg clntVR _ test' = runSmpTestNCfg srvCfg clntVR 2 _test `shouldReturn` ()
where where
_test :: HasCallStack => [THandleSMP c] -> IO () _test :: HasCallStack => [THandleSMP c 'TClient] -> IO ()
_test [h1, h2] = test' h1 h2 _test [h1, h2] = test' h1 h2
_test _ = error "expected 2 handles" _test _ = error "expected 2 handles"
smpTest3 :: forall c. (HasCallStack, Transport c) => TProxy c -> (HasCallStack => THandleSMP c -> THandleSMP c -> THandleSMP c -> IO ()) -> Expectation smpTest3 :: forall c. (HasCallStack, Transport c) => TProxy c -> (HasCallStack => THandleSMP c 'TClient -> THandleSMP c 'TClient -> THandleSMP c 'TClient -> IO ()) -> Expectation
smpTest3 _ test' = smpTestN 3 _test smpTest3 _ test' = smpTestN 3 _test
where where
_test :: HasCallStack => [THandleSMP c] -> IO () _test :: HasCallStack => [THandleSMP c 'TClient] -> IO ()
_test [h1, h2, h3] = test' h1 h2 h3 _test [h1, h2, h3] = test' h1 h2 h3
_test _ = error "expected 3 handles" _test _ = error "expected 3 handles"
smpTest4 :: forall c. (HasCallStack, Transport c) => TProxy c -> (HasCallStack => THandleSMP c -> THandleSMP c -> THandleSMP c -> THandleSMP c -> IO ()) -> Expectation smpTest4 :: forall c. (HasCallStack, Transport c) => TProxy c -> (HasCallStack => THandleSMP c 'TClient -> THandleSMP c 'TClient -> THandleSMP c 'TClient -> THandleSMP c 'TClient -> IO ()) -> Expectation
smpTest4 _ test' = smpTestN 4 _test smpTest4 _ test' = smpTestN 4 _test
where where
_test :: HasCallStack => [THandleSMP c] -> IO () _test :: HasCallStack => [THandleSMP c 'TClient] -> IO ()
_test [h1, h2, h3, h4] = test' h1 h2 h3 h4 _test [h1, h2, h3, h4] = test' h1 h2 h3 h4
_test _ = error "expected 4 handles" _test _ = error "expected 4 handles"
+137
View File
@@ -0,0 +1,137 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
module SMPProxyTests where
import AgentTests.FunctionalAPITests (runRight_)
import Data.ByteString.Char8 (ByteString)
import SMPAgentClient (testSMPServer, testSMPServer2)
import SMPClient
import qualified SMPClient as SMP
import ServerTests (decryptMsgV3, sendRecv)
import Simplex.Messaging.Client
import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Protocol
import Simplex.Messaging.Server.Env.STM (ServerConfig (..))
import Simplex.Messaging.Transport
import Simplex.Messaging.Version (mkVersionRange)
import Test.Hspec
import UnliftIO
smpProxyTests :: Spec
smpProxyTests = do
describe "server configuration" $ do
it "refuses proxy handshake unless enabled" testNoProxy
it "checks basic auth in proxy requests" testProxyAuth
describe "proxy requests" $ do
describe "bad relay URIs" $ do
xit "host not resolved" todo
xit "when SMP port blackholed" todo
xit "no SMP service at host/port" todo
xit "bad SMP fingerprint" todo
xit "batching proxy requests" todo
describe "forwarding requests" $ do
describe "deliver message via SMP proxy" $ do
it "same server" $
withSmpServerConfigOn (transport @TLS) proxyCfg testPort $ \_ -> do
let proxyServ = SMPServer SMP.testHost SMP.testPort SMP.testKeyHash
let relayServ = proxyServ
deliverMessageViaProxy proxyServ relayServ C.SEd448 "hello 1" "hello 2"
it "different servers" $
withSmpServerConfigOn (transport @TLS) proxyCfg testPort $ \_ ->
withSmpServerConfigOn (transport @TLS) cfgV7 testPort2 $ \_ -> do
let proxyServ = SMPServer SMP.testHost SMP.testPort SMP.testKeyHash
let relayServ = SMPServer SMP.testHost SMP.testPort2 SMP.testKeyHash
deliverMessageViaProxy proxyServ relayServ C.SEd448 "hello 1" "hello 2"
xit "max message size, Ed448 keys" $
withSmpServerConfigOn (transport @TLS) proxyCfg testPort $ \_ ->
withSmpServerConfigOn (transport @TLS) cfgV7 testPort2 $ \_ -> do
g <- C.newRandom
msg <- atomically $ C.randomBytes maxMessageLength g
msg' <- atomically $ C.randomBytes maxMessageLength g
let proxyServ = SMPServer SMP.testHost SMP.testPort SMP.testKeyHash
let relayServ = SMPServer SMP.testHost SMP.testPort2 SMP.testKeyHash
deliverMessageViaProxy proxyServ relayServ C.SEd448 msg msg'
it "max message size, Ed25519 keys" $
withSmpServerConfigOn (transport @TLS) proxyCfg testPort $ \_ ->
withSmpServerConfigOn (transport @TLS) cfgV7 testPort2 $ \_ -> do
g <- C.newRandom
msg <- atomically $ C.randomBytes maxMessageLength g
msg' <- atomically $ C.randomBytes maxMessageLength g
let proxyServ = SMPServer SMP.testHost SMP.testPort SMP.testKeyHash
let relayServ = SMPServer SMP.testHost SMP.testPort2 SMP.testKeyHash
deliverMessageViaProxy proxyServ relayServ C.SEd25519 msg msg'
it "max message size, X25519 keys" $
withSmpServerConfigOn (transport @TLS) proxyCfg testPort $ \_ ->
withSmpServerConfigOn (transport @TLS) cfgV7 testPort2 $ \_ -> do
g <- C.newRandom
msg <- atomically $ C.randomBytes maxMessageLength g
msg' <- atomically $ C.randomBytes maxMessageLength g
let proxyServ = SMPServer SMP.testHost SMP.testPort SMP.testKeyHash
let relayServ = SMPServer SMP.testHost SMP.testPort2 SMP.testKeyHash
deliverMessageViaProxy proxyServ relayServ C.SX25519 msg msg'
xit "sender-proxy-relay-recipient works" todo
xit "similar timing for proxied and direct sends" todo
deliverMessageViaProxy :: (C.AlgorithmI a, C.AuthAlgorithm a) => SMPServer -> SMPServer -> C.SAlgorithm a -> ByteString -> ByteString -> IO ()
deliverMessageViaProxy proxyServ relayServ alg msg msg' = do
g <- C.newRandom
-- set up proxy
Right pc <- getProtocolClient g (1, proxyServ, Nothing) defaultSMPClientConfig {serverVRange = mkVersionRange batchCmdsSMPVersion sendingProxySMPVersion} Nothing (\_ -> pure ())
THAuthClient {} <- maybe (fail "getProtocolClient returned no thAuth") pure $ thAuth $ thParams pc
-- set up relay
msgQ <- newTBQueueIO 4
Right rc <- getProtocolClient g (2, relayServ, Nothing) defaultSMPClientConfig {serverVRange = mkVersionRange batchCmdsSMPVersion authCmdsSMPVersion} (Just msgQ) (\_ -> pure ())
runRight_ $ do
-- prepare receiving queue
(rPub, rPriv) <- atomically $ C.generateAuthKeyPair alg g
(rdhPub, rdhPriv :: C.PrivateKeyX25519) <- atomically $ C.generateKeyPair g
QIK {rcvId, sndId, rcvPublicDhKey = srvDh} <- createSMPQueue rc (rPub, rPriv) rdhPub (Just "correct") SMSubscribe
let dec = decryptMsgV3 $ C.dh' srvDh rdhPriv
-- get proxy session
(sessId, v, relayKey) <- createSMPProxySession pc relayServ (Just "correct")
-- send via proxy to unsecured queue
proxySMPMessage pc sessId v relayKey Nothing sndId noMsgFlags msg
-- receive 1
(_tSess, _v, _sid, _ety, MSG RcvMessage {msgId, msgBody = EncRcvMsgBody encBody}) <- atomically $ readTBQueue msgQ
liftIO $ dec msgId encBody `shouldBe` Right msg
ackSMPMessage rc rPriv rcvId msgId
-- secure queue
(sPub, sPriv) <- atomically $ C.generateAuthKeyPair alg g
secureSMPQueue rc rPriv rcvId sPub
-- send via proxy to secured queue
proxySMPMessage pc sessId v relayKey (Just sPriv) sndId noMsgFlags msg'
-- receive 2
(_tSess, _v, _sid, _ety, MSG RcvMessage {msgId = msgId', msgBody = EncRcvMsgBody encBody'}) <- atomically $ readTBQueue msgQ
liftIO $ dec msgId' encBody' `shouldBe` Right msg'
ackSMPMessage rc rPriv rcvId msgId'
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 'TClient) -> 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 'TClient) -> do
(_, _s, (_corrId, _entityId, reply)) <- sendRecv th (Nothing, "0", "", PRXY testSMPServer2 $ Just "wrong")
reply `shouldBe` Right (ERR AUTH)
where
proxyCfgAuth = proxyCfg {newQueueBasicAuth = Just "correct"}
todo :: IO ()
todo = do
fail "TODO"
+18 -19
View File
@@ -78,13 +78,13 @@ pattern Ids rId sId srvDh <- IDS (QIK rId sId srvDh)
pattern Msg :: MsgId -> MsgBody -> BrokerMsg pattern Msg :: MsgId -> MsgBody -> BrokerMsg
pattern Msg msgId body <- MSG RcvMessage {msgId, msgBody = EncRcvMsgBody body} pattern Msg msgId body <- MSG RcvMessage {msgId, msgBody = EncRcvMsgBody body}
sendRecv :: forall c p. (Transport c, PartyI p) => THandleSMP c -> (Maybe TransmissionAuth, ByteString, ByteString, Command p) -> IO (SignedTransmission ErrorType BrokerMsg) sendRecv :: forall c p. (Transport c, PartyI p) => THandleSMP c 'TClient -> (Maybe TransmissionAuth, ByteString, ByteString, Command p) -> IO (SignedTransmission ErrorType BrokerMsg)
sendRecv h@THandle {params} (sgn, corrId, qId, cmd) = do sendRecv h@THandle {params} (sgn, corrId, qId, cmd) = do
let TransmissionForAuth {tToSend} = encodeTransmissionForAuth params (CorrId corrId, qId, cmd) let TransmissionForAuth {tToSend} = encodeTransmissionForAuth params (CorrId corrId, qId, cmd)
Right () <- tPut1 h (sgn, tToSend) Right () <- tPut1 h (sgn, tToSend)
tGet1 h tGet1 h
signSendRecv :: forall c p. (Transport c, PartyI p) => THandleSMP c -> C.APrivateAuthKey -> (ByteString, ByteString, Command p) -> IO (SignedTransmission ErrorType BrokerMsg) signSendRecv :: forall c p. (Transport c, PartyI p) => THandleSMP c 'TClient -> C.APrivateAuthKey -> (ByteString, ByteString, Command p) -> IO (SignedTransmission ErrorType BrokerMsg)
signSendRecv h@THandle {params} (C.APrivateAuthKey a pk) (corrId, qId, cmd) = do signSendRecv h@THandle {params} (C.APrivateAuthKey a pk) (corrId, qId, cmd) = do
let TransmissionForAuth {tForAuth, tToSend} = encodeTransmissionForAuth params (CorrId corrId, qId, cmd) let TransmissionForAuth {tForAuth, tToSend} = encodeTransmissionForAuth params (CorrId corrId, qId, cmd)
Right () <- tPut1 h (authorize tForAuth, tToSend) Right () <- tPut1 h (authorize tForAuth, tToSend)
@@ -93,17 +93,17 @@ signSendRecv h@THandle {params} (C.APrivateAuthKey a pk) (corrId, qId, cmd) = do
authorize t = case a of authorize t = case a of
C.SEd25519 -> Just . TASignature . C.ASignature C.SEd25519 $ C.sign' pk t C.SEd25519 -> Just . TASignature . C.ASignature C.SEd25519 $ C.sign' pk t
C.SEd448 -> Just . TASignature . C.ASignature C.SEd448 $ C.sign' pk t C.SEd448 -> Just . TASignature . C.ASignature C.SEd448 $ C.sign' pk t
C.SX25519 -> (\THandleAuth {peerPubKey} -> TAAuthenticator $ C.cbAuthenticate peerPubKey pk (C.cbNonce corrId) t) <$> thAuth params C.SX25519 -> (\THAuthClient {serverPeerPubKey = k} -> TAAuthenticator $ C.cbAuthenticate k pk (C.cbNonce corrId) t) <$> thAuth params
#if !MIN_VERSION_base(4,18,0) #if !MIN_VERSION_base(4,18,0)
_sx448 -> undefined -- ghc8107 fails to the branch excluded by types _sx448 -> undefined -- ghc8107 fails to the branch excluded by types
#endif #endif
tPut1 :: Transport c => THandle v c -> SentRawTransmission -> IO (Either TransportError ()) tPut1 :: Transport c => THandle v c 'TClient -> SentRawTransmission -> IO (Either TransportError ())
tPut1 h t = do tPut1 h t = do
[r] <- tPut h [Right t] [r] <- tPut h [Right t]
pure r pure r
tGet1 :: (ProtocolEncoding v err cmd, Transport c) => THandle v c -> IO (SignedTransmission err cmd) tGet1 :: (ProtocolEncoding v err cmd, Transport c) => THandle v c 'TClient -> IO (SignedTransmission err cmd)
tGet1 h = do tGet1 h = do
[r] <- liftIO $ tGet h [r] <- liftIO $ tGet h
pure r pure r
@@ -555,12 +555,12 @@ testWithStoreLog at@(ATransport t) =
logSize testStoreLogFile `shouldReturn` 1 logSize testStoreLogFile `shouldReturn` 1
removeFile testStoreLogFile removeFile testStoreLogFile
where where
runTest :: Transport c => TProxy c -> (THandleSMP c -> IO ()) -> ThreadId -> Expectation runTest :: Transport c => TProxy c -> (THandleSMP c 'TClient -> IO ()) -> ThreadId -> Expectation
runTest _ test' server = do runTest _ test' server = do
testSMPClient test' `shouldReturn` () testSMPClient test' `shouldReturn` ()
killThread server killThread server
runClient :: Transport c => TProxy c -> (THandleSMP c -> IO ()) -> Expectation runClient :: Transport c => TProxy c -> (THandleSMP c 'TClient -> IO ()) -> Expectation
runClient _ test' = testSMPClient test' `shouldReturn` () runClient _ test' = testSMPClient test' `shouldReturn` ()
logSize :: FilePath -> IO Int logSize :: FilePath -> IO Int
@@ -653,12 +653,12 @@ testRestoreMessages at@(ATransport t) =
removeFile testStoreMsgsFile removeFile testStoreMsgsFile
removeFile testServerStatsBackupFile removeFile testServerStatsBackupFile
where where
runTest :: Transport c => TProxy c -> (THandleSMP c -> IO ()) -> ThreadId -> Expectation runTest :: Transport c => TProxy c -> (THandleSMP c 'TClient -> IO ()) -> ThreadId -> Expectation
runTest _ test' server = do runTest _ test' server = do
testSMPClient test' `shouldReturn` () testSMPClient test' `shouldReturn` ()
killThread server killThread server
runClient :: Transport c => TProxy c -> (THandleSMP c -> IO ()) -> Expectation runClient :: Transport c => TProxy c -> (THandleSMP c 'TClient -> IO ()) -> Expectation
runClient _ test' = testSMPClient test' `shouldReturn` () runClient _ test' = testSMPClient test' `shouldReturn` ()
checkStats :: ServerStatsData -> [RecipientId] -> Int -> Int -> Expectation checkStats :: ServerStatsData -> [RecipientId] -> Int -> Int -> Expectation
@@ -727,15 +727,15 @@ testRestoreExpireMessages at@(ATransport t) =
Right ServerStatsData {_msgExpired} <- strDecode <$> B.readFile testServerStatsBackupFile Right ServerStatsData {_msgExpired} <- strDecode <$> B.readFile testServerStatsBackupFile
_msgExpired `shouldBe` 2 _msgExpired `shouldBe` 2
where where
runTest :: Transport c => TProxy c -> (THandleSMP c -> IO ()) -> ThreadId -> Expectation runTest :: Transport c => TProxy c -> (THandleSMP c 'TClient -> IO ()) -> ThreadId -> Expectation
runTest _ test' server = do runTest _ test' server = do
testSMPClient test' `shouldReturn` () testSMPClient test' `shouldReturn` ()
killThread server killThread server
runClient :: Transport c => TProxy c -> (THandleSMP c -> IO ()) -> Expectation runClient :: Transport c => TProxy c -> (THandleSMP c 'TClient -> IO ()) -> Expectation
runClient _ test' = testSMPClient test' `shouldReturn` () runClient _ test' = testSMPClient test' `shouldReturn` ()
createAndSecureQueue :: Transport c => THandleSMP c -> SndPublicAuthKey -> IO (SenderId, RecipientId, RcvPrivateAuthKey, RcvDhSecret) createAndSecureQueue :: Transport c => THandleSMP c 'TClient -> SndPublicAuthKey -> IO (SenderId, RecipientId, RcvPrivateAuthKey, RcvDhSecret)
createAndSecureQueue h sPub = do createAndSecureQueue h sPub = do
g <- C.newRandom g <- C.newRandom
(rPub, rKey) <- atomically $ C.generateAuthKeyPair C.SEd448 g (rPub, rKey) <- atomically $ C.generateAuthKeyPair C.SEd448 g
@@ -759,8 +759,8 @@ testTiming (ATransport t) =
timingTests :: [(C.AuthAlg, C.AuthAlg, Int)] timingTests :: [(C.AuthAlg, C.AuthAlg, Int)]
timingTests = timingTests =
[ (C.AuthAlg C.SEd25519, C.AuthAlg C.SEd25519, 200), -- correct key type [ (C.AuthAlg C.SEd25519, C.AuthAlg C.SEd25519, 200), -- correct key type
-- (C.AuthAlg C.SEd25519, C.AuthAlg C.SEd448, 150), -- (C.AuthAlg C.SEd25519, C.AuthAlg C.SEd448, 150),
-- (C.AuthAlg C.SEd25519, C.AuthAlg C.SX25519, 200), -- (C.AuthAlg C.SEd25519, C.AuthAlg C.SX25519, 200),
(C.AuthAlg C.SEd448, C.AuthAlg C.SEd25519, 200), (C.AuthAlg C.SEd448, C.AuthAlg C.SEd25519, 200),
(C.AuthAlg C.SEd448, C.AuthAlg C.SEd448, 150), -- correct key type (C.AuthAlg C.SEd448, C.AuthAlg C.SEd448, 150), -- correct key type
(C.AuthAlg C.SEd448, C.AuthAlg C.SX25519, 200), (C.AuthAlg C.SEd448, C.AuthAlg C.SX25519, 200),
@@ -770,7 +770,7 @@ testTiming (ATransport t) =
] ]
timeRepeat n = fmap fst . timeItT . forM_ (replicate n ()) . const timeRepeat n = fmap fst . timeItT . forM_ (replicate n ()) . const
similarTime t1 t2 = abs (t2 / t1 - 1) < 0.2 -- normally the difference between "no queue" and "wrong key" is less than 5% similarTime t1 t2 = abs (t2 / t1 - 1) < 0.2 -- normally the difference between "no queue" and "wrong key" is less than 5%
testSameTiming :: forall c. Transport c => THandleSMP c -> THandleSMP c -> (C.AuthAlg, C.AuthAlg, Int) -> Expectation testSameTiming :: forall c. Transport c => THandleSMP c 'TClient -> THandleSMP c 'TClient -> (C.AuthAlg, C.AuthAlg, Int) -> Expectation
testSameTiming rh sh (C.AuthAlg goodKeyAlg, C.AuthAlg badKeyAlg, n) = do testSameTiming rh sh (C.AuthAlg goodKeyAlg, C.AuthAlg badKeyAlg, n) = do
g <- C.newRandom g <- C.newRandom
(rPub, rKey) <- atomically $ C.generateAuthKeyPair goodKeyAlg g (rPub, rKey) <- atomically $ C.generateAuthKeyPair goodKeyAlg g
@@ -791,10 +791,11 @@ testTiming (ATransport t) =
runTimingTest sh badKey sId $ _SEND "hello" runTimingTest sh badKey sId $ _SEND "hello"
where where
runTimingTest :: PartyI p => THandleSMP c -> C.APrivateAuthKey -> ByteString -> Command p -> IO () runTimingTest :: PartyI p => THandleSMP c 'TClient -> C.APrivateAuthKey -> ByteString -> Command p -> IO ()
runTimingTest h badKey qId cmd = do runTimingTest h badKey qId cmd = do
threadDelay 100000 threadDelay 100000
_ <- timeRepeat n $ do -- "warm up" the server _ <- timeRepeat n $ do
-- "warm up" the server
Resp "dabc" _ (ERR AUTH) <- signSendRecv h badKey ("dabc", "1234", cmd) Resp "dabc" _ (ERR AUTH) <- signSendRecv h badKey ("dabc", "1234", cmd)
return () return ()
threadDelay 100000 threadDelay 100000
@@ -930,8 +931,6 @@ instance Eq C.ASignature where
Just Refl -> s == s' Just Refl -> s == s'
_ -> False _ -> False
deriving instance Eq (C.Signature a)
syntaxTests :: ATransport -> Spec syntaxTests :: ATransport -> Spec
syntaxTests (ATransport t) = do syntaxTests (ATransport t) = do
it "unknown command" $ ("", "abcd", "1234", ('H', 'E', 'L', 'L', 'O')) >#> ("", "abcd", "1234", ERR $ CMD UNKNOWN) it "unknown command" $ ("", "abcd", "1234", ('H', 'E', 'L', 'L', 'O')) >#> ("", "abcd", "1234", ERR $ CMD UNKNOWN)
+3 -1
View File
@@ -21,6 +21,7 @@ import GHC.IO.Exception (IOException (..))
import qualified GHC.IO.Exception as IOException import qualified GHC.IO.Exception as IOException
import NtfServerTests (ntfServerTests) import NtfServerTests (ntfServerTests)
import RemoteControl (remoteControlTests) import RemoteControl (remoteControlTests)
import SMPProxyTests (smpProxyTests)
import ServerTests import ServerTests
import Simplex.Messaging.Transport (TLS, Transport (..)) import Simplex.Messaging.Transport (TLS, Transport (..))
import Simplex.Messaging.Transport.WebSockets (WS) import Simplex.Messaging.Transport.WebSockets (WS)
@@ -46,7 +47,7 @@ main = do
$ do $ do
describe "Agent SQLite schema dump" schemaDumpTest describe "Agent SQLite schema dump" schemaDumpTest
describe "Core tests" $ do describe "Core tests" $ do
describe "Batching tests" batchingTests xdescribe "Batching tests" batchingTests
describe "Encoding tests" encodingTests describe "Encoding tests" encodingTests
describe "Protocol error tests" protocolErrorTests describe "Protocol error tests" protocolErrorTests
describe "Version range" versionRangeTests describe "Version range" versionRangeTests
@@ -59,6 +60,7 @@ main = do
describe "SMP server via WebSockets" $ serverTests (transport @WS) describe "SMP server via WebSockets" $ serverTests (transport @WS)
describe "Notifications server" $ ntfServerTests (transport @TLS) describe "Notifications server" $ ntfServerTests (transport @TLS)
describe "SMP client agent" $ agentTests (transport @TLS) describe "SMP client agent" $ agentTests (transport @TLS)
describe "SMP proxy" smpProxyTests
describe "XFTP" $ do describe "XFTP" $ do
describe "XFTP server" xftpServerTests describe "XFTP server" xftpServerTests
describe "XFTP file description" fileDescriptionTests describe "XFTP file description" fileDescriptionTests