diff --git a/apps/smp-server/Main.hs b/apps/smp-server/Main.hs index c40ed3f79..e29bb804a 100644 --- a/apps/smp-server/Main.hs +++ b/apps/smp-server/Main.hs @@ -39,7 +39,6 @@ serverConfig = msgQueueQuota = 256, queueIdBytes = 24, msgIdBytes = 24, -- must be at least 24 bytes, it is used as 192-bit nonce for XSalsa20 - blockSize = 16 * 1024, -- TODO move to Protocol -- below parameters are set based on ini file /etc/opt/simplex/smp-server.ini transports = undefined, storeLog = undefined, diff --git a/migrations/20210101_initial.sql b/migrations/20210101_initial.sql index eb70363d0..a43d788f0 100644 --- a/migrations/20210101_initial.sql +++ b/migrations/20210101_initial.sql @@ -11,10 +11,11 @@ CREATE TABLE IF NOT EXISTS rcv_queues( rcv_id BLOB NOT NULL, conn_alias BLOB NOT NULL, rcv_private_key BLOB NOT NULL, + e2e_priv_key BLOB NOT NULL, + e2e_snd_pub_key BLOB, + e2e_dh_secret BLOB, snd_id BLOB NOT NULL, snd_key BLOB, - decrypt_key BLOB NOT NULL, - verify_key BLOB, status TEXT NOT NULL, PRIMARY KEY (host, port, rcv_id), FOREIGN KEY (host, port) REFERENCES servers (host, port), @@ -31,8 +32,8 @@ CREATE TABLE IF NOT EXISTS snd_queues( snd_id BLOB NOT NULL, conn_alias BLOB NOT NULL, snd_private_key BLOB NOT NULL, - encrypt_key BLOB NOT NULL, - sign_key BLOB NOT NULL, + e2e_pub_key BLOB NOT NULL, + e2e_dh_secret BLOB NOT NULL, status TEXT NOT NULL, PRIMARY KEY (host, port, snd_id), FOREIGN KEY (host, port) REFERENCES servers (host, port), @@ -87,7 +88,6 @@ CREATE TABLE IF NOT EXISTS rcv_messages( internal_rcv_id INTEGER NOT NULL, internal_id INTEGER NOT NULL, external_snd_id INTEGER NOT NULL, - external_snd_ts TEXT NOT NULL, broker_id BLOB NOT NULL, broker_ts TEXT NOT NULL, rcv_status TEXT NOT NULL, diff --git a/migrations/20210624_confirmations.sql b/migrations/20210624_confirmations.sql index f7b1e8e85..798471e78 100644 --- a/migrations/20210624_confirmations.sql +++ b/migrations/20210624_confirmations.sql @@ -1,6 +1,7 @@ CREATE TABLE conn_confirmations ( confirmation_id BLOB NOT NULL PRIMARY KEY, conn_alias BLOB NOT NULL REFERENCES connections ON DELETE CASCADE, + e2e_snd_pub_key BLOB NOT NULL, sender_key BLOB NOT NULL, sender_conn_info BLOB NOT NULL, accepted INTEGER NOT NULL, diff --git a/protocol/agent-protocol.md b/protocol/agent-protocol.md index e357ae66a..922cf3a41 100644 --- a/protocol/agent-protocol.md +++ b/protocol/agent-protocol.md @@ -142,17 +142,15 @@ agentMessage = helloMsg / replyQueueMsg / msgPadding = *OCTET ; optional random bytes to get messages to the same size (as defined in SMP message size) -helloMsg = %s"HELLO" SP signatureVerificationKey [SP %s"NO_ACK"] -; NO_ACK means that acknowledgements to client messages will NOT be sent in this connection by the agent that sent `HELLO` message. -signatureVerificationKey = encoded +helloMsg = %s"HELLO" replyQueueMsg = %s"REPLY" SP connectionRequest ; `connectionRequest` is defined below ; this message can only be sent by the second connection party -clientMsg = %s"MSG" SP size CRLF clientMsgBody CRLF ; CRLF is in addition to CRLF in decryptedSmpMessageBody -size = 1*DIGIT +clientMsg = %s"MSG" SP clientMsgBody clientMsgBody = *OCTET +; TODO remove and move to "public" header invitationMsg = %s"INV" SP connReqInvitation SP connInfo ; `connReqInvitation` and `connInfo` are defined below @@ -303,7 +301,7 @@ messageError = %s"MERR" SP agentMsgId SP message = %s"MSG" SP msgIntegrity SP recipientMeta SP brokerMeta SP senderMeta SP binaryMsg recipientMeta = %s"R=" agentMsgId "," agentTimestamp ; receiving agent message metadata brokerMeta = %s"B=" brokerMsgId "," brokerTimestamp ; broker (server) message metadata -senderMeta = %s"S=" agentMsgId "," agentTimestamp ; sending agent message metadata +senderMeta = %s"S=" agentMsgId ; sending agent message ID brokerMsgId = encoded brokerTimestamp = msgIntegrity = ok / msgIntegrityError diff --git a/protocol/simplex-messaging.md b/protocol/simplex-messaging.md index b4e008cf7..7e4be2200 100644 --- a/protocol/simplex-messaging.md +++ b/protocol/simplex-messaging.md @@ -14,6 +14,7 @@ - [Simplex queue IDs](#simplex-queue-ids) - [Server security requirements](#server-security-requirements) - [Message delivery notifications](#message-delivery-notifications) +- [SMP Transmission structure](#smp-transmission-structure) - [SMP commands](#smp-commands) - [Correlating responses with commands](#correlating-responses-with-commands) - [Command authentication](#command-authentication) @@ -116,13 +117,22 @@ The SMP queue URIs MUST include server identity, queue hostname, an optional por The [ABNF][8] syntax of the queue URI is: ```abnf -queueURI = %s"smp://" smpServer "/" queueId ["#"] -smpServer = serverIdentity "@" srvHost [":" port] +queueURI = %s"smp://" smpServer "/" queueId "#" recipientDhPublicKey +smpServer = serverIdentity "@" srvHost [":" port] srvHost = ; RFC1123, RFC5891 port = 1*DIGIT serverIdentity = base64url queueId = base64url base64url = ; RFC4648, section 5 +recipientDhPublicKey = dhPublicKey +dhPublicKey = encryptionScheme ":" x509UrlEncoded +; the recipient's key for DH exchange to derive the secret +; that the sender will use to encrypt delivered messages + +encryptionScheme = %s"x25519" +; x25519 scheme means [NaCl crypto_box][16] encryption scheme (curve25519xsalsa20poly1305). + +x509UrlEncoded = ``` `hostname` can be IP address or domain name, as defined in RFC 1123, section 2.1. @@ -345,24 +355,35 @@ The clients can optionally instruct a dedicated push notification server to subs - `subscribeNotifications` (`"NSUB"`) - see [Subscribe to queue notifications](#subscribe-to-queue-notifications). - `messageNotification` (`"NMSG"`) - see [Deliver message notification](#deliver-message-notification). -## SMP commands +## SMP Transmission structure -Commands syntax below is provided using [ABNF][8] with [case-sensitive strings extension][8a]. +Each transport block (SMP transmission) has a fixed size of 16384 bytes for traffic uniformity. -Each transmission between the client and the server must have this format/syntax (after the decryption): +Some parts of SMP transmission are padded to a fixed size; this padding is uniformly added as a word16 encoded in network byte order - see `paddedString` syntax. -```abnf -transmission = [signature] SP signedSize SP signed SP pad ; pad to the fixed block size -signedSize = 1*DIGIT -signed = sessionIdentifier SP [corrId] SP [queueId] SP cmd ; corrId is required in client commands and server responses, - ; corrId is empty in server notifications. -cmd = ping / recipientCmd / send / subscribeNotifications / serverMsg -recipientCmd = create / subscribe / secure / enableNotifications / - acknowledge / suspend / delete -serverMsg = queueIds / message / notifierId / messageNotification / - unsubscribed / ok / error -corrId = 1*(%x21-7F) ; any characters other than control/whitespace -queueId = encoded ; empty queue ID is used with "create" command +In places where some part of the transmission should be padded, the syntax for `paddedNotation` is used: + +``` +paddedString = originalLength string pad +originalLength = 2*2 OCTET +pad = N*N"#" ; where N = paddedLength - originalLength - 2 + +paddedNotation = +; string - un-padded string +; paddedLength - required length after padding, including 2 bytes for originalLength +``` + +Each transmission between the client and the server must have this format/syntax: + +``` +paddedTransmission = +transmission = [signature] SP signed +signed = sessionIdentifier SP [corrId] SP [queueId] SP smpCommand +; corrId is required in client commands and server responses, +; it is empty in server notifications. +corrId = 1*32(%x21-7F) ; any characters other than control/whitespace +queueId = encoded ; max 32 bytes when decoded (24 bytes is used), +; empty queue ID is used with "create" command and in some server responses signature = encoded ; empty signature can be used with "send" before the queue is secured with secure command ; signature is always empty with "ping" and "serverMsg" @@ -371,6 +392,18 @@ encoded = `base64` encoding should be used with padding, as defined in section 4 of [RFC 4648][9] +## SMP commands + +Commands syntax below is provided using [ABNF][8] with [case-sensitive strings extension][8a]. + +```abnf +smpCommand = ping / recipientCmd / send / subscribeNotifications / serverMsg +recipientCmd = create / subscribe / secure / enableNotifications / + acknowledge / suspend / delete +serverMsg = queueIds / message / notifierId / messageNotification / + unsubscribed / ok / error +``` + The syntax of specific commands and responses is defined below. ### Correlating responses with commands @@ -534,10 +567,16 @@ Currently SMP defines only one command that can be used by senders - `send` mess This command is sent to the server by the sender both to confirm the queue after the sender received out-of-band message from the recipient and to send messages after the queue is secured: ```abnf -send = %s"SEND" SP size SP msgBody SP -; the last SP is in addition to SP in the transmission -size = 1*DIGIT ; size in bytes -msgBody = *OCTET ; any binary content of specified size +send = %s"SEND" SP smpEncMessage +smpEncMessage = smpPubHeader sentMsgBody ; message up to 15968 bytes +smpPubHeader = smpClientVersion encodedLenKey +smpClientVersion = word16 +encodedLenKey = keyLen x509binary +keyLen = word16 +x509binary = +sentMsgBody = 15842*15842 OCTET +; E2E-encrypted smpClientMessage padded to 15842 bytes before encryption +word16 = 2*2 OCTET ``` The first message is sent to confirm the queue - it should contain sender's server key (see decrypted message syntax below) - this first message must be sent without signature. @@ -555,15 +594,85 @@ Until the queue is secured, the server should accept any number of unsigned mess The body should be encrypted with the recipient's "public" key (`EK`); once decrypted it must have this format: ```abnf -decryptedBody = [clientHeader] CRLF clientBody CRLF -clientHeader = senderKeyMsg -senderKeyMsg = %s"KEY" SP senderKey -senderKey = signatureScheme ":" x509encoded ; the sender's public key to sign SEND commands for this queue -clientBody = *OCTET +sentMsgBody = +smpClientMessage = smpPrivHeader clientMsgBody +smpPrivHeader = emptyHeader / smpConfirmationHeader +emptyHeader = " " +smpConfirmationHeader = %s"K" senderKey +senderKey = encodedLenKey ; the sender's public key to sign SEND commands for this queue +clientMsgBody = *OCTET ; up to 15784 in case of emptyHeader ``` `clientHeader` in the initial unsigned message is used to transmit sender's server key and can be used in the future revisions of SMP protocol for other purposes. +SMP transmission structure for sent messages: + +``` +------- transmission (= 16384 bytes) + 2 | originalLength + 398- | signature SP sessionId SP corrId SP queueId SP %s"SEND" SP + ....... smpEncMessage (= 15968 bytes) + 126- | smpPubHeader + 24 | nonce for smpClientMessage + ------- smpClientMessage (E2E encrypted, = 15842 bytes) + 2 | originalLength + 16- | smpPrivHeader + ....... + | clientMsgBody (<= 15784 bytes) + ....... + 0+ | smpClientMessage pad + ------- smpClientMessage end + 16 | auth tag for smpClientMessage + ....... smpEncMessage end + 16+ | transmission pad +------- transmission end +``` + +SMP transmission structure for received messages: + +``` +------- transmission (= 16384 bytes) + 2 | originalLength + 398- | signature SP sessionId SP corrId SP queueId SP %s"MSG" SP msgId SP timestamp SP + ------- serverEncryptedMsg (= 15986 bytes) + 2 | originalLength + ....... smpEncMessage (= 15968 bytes) + 126- | smpPubHeader + 24 | nonce for smpClientMessage + ------- smpClientMessage (E2E encrypted, = 15842 bytes) + 2 | originalLength + 16- | smpPrivHeader + ....... clientMsgBody (<= 15784 bytes) + -- TODO move internal structure to agent protocol + 16- | agentPublicHeader + ....... E2E double-ratchet encrypted (= 15768) + 96 | double-ratchet header + 16 | double-ratchet header auth tag + 24 | double-ratchet header iv + ------- encrypted agent message (= 15616 bytes) + 2 | originalLength + 122 (90) | agentHeader + 4 | %s"MSG" SP + ....... + | application message (<= 15488 bytes) + ....... + 0+ | encrypted agent message pad + ------- encrypted agent message end + 16 | auth tag (IV generated from chain ratchet) + ....... E2E double-ratchet encrypted end + | + ....... clientMsgBody end + 0+ | smpClientMessage pad + ------- smpClientMessage end + 16 | auth tag for smpClientMessage + ....... smpEncMessage end + 16 | auth tag (msgId is used as nonce) + 0+ | serverEncryptedMsg pad + ------- serverEncryptedMsg end + 0+ | transmission pad +------- transmission end +``` + ### Notifier commands #### Subscribe to queue notifications @@ -591,9 +700,9 @@ See its syntax in [Create queue command](#create-queue-command) The server must deliver messages to all subscribed simplex queues on the currently open transport connection. The syntax for the message delivery is: ```abnf -message = %s"MSG" SP encryptedMessage -encryptedMessage = -sentMessage = msgId SP timestamp SP size SP msgBody SP +message = %s"MSG" SP msgId SP timestamp SP encryptedMsgBody +encryptedMsgBody = ; server-encrypted padded sent msgBody +paddedSentMsgBody = ; maxMessageLength = 15968 msgId = encoded timestamp = ``` @@ -602,8 +711,6 @@ timestamp = `timestamp` - the UTC time when the server received the message from the sender, must be in date-time format defined by [RFC 3339][10] -`msgBody` - see syntax in [Send message](#send-message) - When server delivers the messages to the recipient, message body should be encrypted with the secret derived from DH exchange using the keys passed during the queue creation and returned with `queueIds` response. This is done to prevent the possibility of correlation of incoming and outgoing traffic of SMP server inside transport protocol. @@ -637,24 +744,24 @@ No further messages should be delivered to unsubscribed transport connection. #### Error responses - incorrect block format, encoding or signature size (`BLOCK`). +- missing or different session ID - tls-unique binding of TLS transport (`SESSION`) - command errors (`CMD`): - error parsing command (`SYNTAX`) - prohibited command (`PROHIBITED`) - any server response sent from client or `ACK` sent without active subscription or without message delivery. - - incorrect RSA key size in `NEW` or `KEY` commands - only 1024, 2048 and 4096-bit keys are allowed (`KEY_SIZE`). - transmission has no required signature or queue ID (`NO_AUTH`) - transmission has unexpected credentials (`HAS_AUTH`) - transmission has no required queue ID (`NO_QUEUE`) - authentication error (`AUTH`) - incorrect signature, unknown (or suspended) queue, sender's ID is used in place of recipient's and vice versa, and some other cases (see [Send message](#send-message) command). - message queue quota exceeded error (`QUOTA`) - too many messages were sent to the message queue. Further messages can only be sent after the recipient retrieves the messages. -- incorrect message body size (`SIZE`). +- sent message is too large (> 15968) to be delivered (`LARGE_MSG`). - internal server error (`INTERNAL`). The syntax for error responses: ```abnf error = %s"ERR" SP errorType -errorType = %s"BLOCK" / %s"CMD" SP cmdError / %s"AUTH" / %s"SIZE" /%s"INTERNAL" -cmdError = %s"SYNTAX" / %s"PROHIBITED" / %s"KEY_SIZE" / %s"NO_AUTH" / %s"HAS_AUTH" / %s"NO_QUEUE" +errorType = %s"BLOCK" / %s"SESSION" / %s"CMD" SP cmdError / %s"AUTH" / %s"LARGE_MSG" /%s"INTERNAL" +cmdError = %s"SYNTAX" / %s"PROHIBITED" / %s"NO_AUTH" / %s"HAS_AUTH" / %s"NO_QUEUE" ``` Server implementations must aim to respond within the same time for each command in all cases when `"ERR AUTH"` response is required to prevent timing attacks (e.g., the server should perform signature verification even when the queue does not exist on the server or the signature of different size is sent, using any RSA key with the same size as the signature size). diff --git a/src/Simplex/Messaging/Agent.hs b/src/Simplex/Messaging/Agent.hs index 63fc15274..d5df1e746 100644 --- a/src/Simplex/Messaging/Agent.hs +++ b/src/Simplex/Messaging/Agent.hs @@ -62,7 +62,7 @@ import Control.Monad.Except import Control.Monad.IO.Unlift (MonadUnliftIO) import Control.Monad.Reader import Crypto.Random (MonadRandom) -import Data.Bifunctor (first, second) +import Data.Bifunctor (second) import Data.ByteString.Char8 (ByteString) import qualified Data.ByteString.Char8 as B import Data.Composition ((.:), (.:.)) @@ -83,7 +83,8 @@ import Simplex.Messaging.Agent.Store import Simplex.Messaging.Agent.Store.SQLite (SQLiteStore) import Simplex.Messaging.Client (SMPServerTransmission) import qualified Simplex.Messaging.Crypto as C -import Simplex.Messaging.Protocol (MsgBody, SndPublicVerifyKey) +import Simplex.Messaging.Parsers (parse) +import Simplex.Messaging.Protocol (MsgBody) import qualified Simplex.Messaging.Protocol as SMP import Simplex.Messaging.Transport (ATransport (..), TProxy, Transport (..), currentSMPVersionStr, loadTLSServerParams, runTransportServer) import Simplex.Messaging.Util (bshow, tryError, unlessM) @@ -265,50 +266,50 @@ processCommand c (connId, cmd) = case cmd of newConn :: AgentMonad m => AgentClient -> ConnId -> SConnectionMode c -> m (ConnId, ConnectionRequest c) newConn c connId cMode = do srv <- getSMPServer - (rq, qUri, encryptKey) <- newRcvQueue c srv + (rq, qUri) <- newRcvQueue c srv g <- asks idsDrg let cData = ConnData {connId} connId' <- withStore $ \st -> createRcvConn st g cData rq cMode addSubscription c rq connId' - let crData = ConnReqData simplexChat [qUri] encryptKey + let crData = ConnReqData simplexChat [qUri] ConnectionEncryption pure . (connId',) $ case cMode of SCMInvitation -> CRInvitation crData SCMContact -> CRContact crData joinConn :: AgentMonad m => AgentClient -> ConnId -> ConnectionRequest c -> ConnInfo -> m ConnId -joinConn c connId (CRInvitation (ConnReqData _ (qUri :| _) encryptKey)) cInfo = do - (sq, senderKey, verifyKey) <- newSndQueue qUri encryptKey +joinConn c connId (CRInvitation (ConnReqData _ (qUri :| _) _)) cInfo = do + (sq, smpConf) <- newSndQueue qUri cInfo g <- asks idsDrg cfg <- asks config let cData = ConnData {connId} connId' <- withStore $ \st -> createSndConn st g cData sq - confirmQueue c sq senderKey cInfo - activateQueueJoining c connId' sq verifyKey $ retryInterval cfg + confirmQueue c sq smpConf + activateQueueJoining c connId' sq $ retryInterval cfg pure connId' -joinConn c connId (CRContact (ConnReqData _ (qUri :| _) encryptKey)) cInfo = do +joinConn c connId (CRContact (ConnReqData _ (qUri :| _) _)) cInfo = do (connId', cReq) <- newConn c connId SCMInvitation - sendInvitation c qUri encryptKey cReq cInfo + sendInvitation c qUri cReq cInfo pure connId' -activateQueueJoining :: forall m. AgentMonad m => AgentClient -> ConnId -> SndQueue -> C.APublicVerifyKey -> RetryInterval -> m () -activateQueueJoining c connId sq verifyKey retryInterval = - activateQueue c connId sq verifyKey retryInterval createReplyQueue +activateQueueJoining :: forall m. AgentMonad m => AgentClient -> ConnId -> SndQueue -> RetryInterval -> m () +activateQueueJoining c connId sq retryInterval = + activateQueue c connId sq retryInterval createReplyQueue where createReplyQueue :: m () createReplyQueue = do srv <- getSMPServer - (rq, qUri', encryptKey) <- newRcvQueue c srv + (rq, qUri') <- newRcvQueue c srv addSubscription c rq connId withStore $ \st -> upgradeSndConnToDuplex st connId rq - sendControlMessage c sq . REPLY $ CRInvitation $ ConnReqData CRSSimplex [qUri'] encryptKey + sendControlMessage c sq . REPLY $ CRInvitation $ ConnReqData CRSSimplex [qUri'] ConnectionEncryption -- | Approve confirmation (LET command) in Reader monad allowConnection' :: AgentMonad m => AgentClient -> ConnId -> ConfirmationId -> ConnInfo -> m () allowConnection' c connId confId ownConnInfo = do withStore (`getConn` connId) >>= \case SomeConn _ (RcvConnection _ rq) -> do - AcceptedConfirmation {senderKey} <- withStore $ \st -> acceptConfirmation st confId ownConnInfo - processConfirmation c rq senderKey + AcceptedConfirmation {senderConf} <- withStore $ \st -> acceptConfirmation st confId ownConnInfo + processConfirmation c rq senderConf _ -> throwError $ CMD PROHIBITED -- | Accept contact (ACPT command) in Reader monad @@ -326,10 +327,11 @@ rejectContact' :: AgentMonad m => AgentClient -> ConnId -> InvitationId -> m () rejectContact' _ contactConnId invId = withStore $ \st -> deleteInvitation st contactConnId invId -processConfirmation :: AgentMonad m => AgentClient -> RcvQueue -> SndPublicVerifyKey -> m () -processConfirmation c rq sndKey = do - withStore $ \st -> setRcvQueueStatus st rq Confirmed - secureQueue c rq sndKey +processConfirmation :: AgentMonad m => AgentClient -> RcvQueue -> SMPConfirmation -> m () +processConfirmation c rq@RcvQueue {e2ePrivKey} SMPConfirmation {senderKey, e2ePubKey} = do + let dhSecret = C.dh' e2ePubKey e2ePrivKey + withStore $ \st -> setRcvQueueConfirmedE2E st rq e2ePubKey dhSecret + secureQueue c rq senderKey withStore $ \st -> setRcvQueueStatus st rq Secured -- | Subscribe to receive connection messages (SUB command) in Reader monad @@ -340,8 +342,9 @@ subscribeConnection' c connId = resumeMsgDelivery c connId sq case status (sq :: SndQueue) of Confirmed -> do - conf <- withStore (`getAcceptedConfirmation` connId) - secureQueue c rq $ senderKey (conf :: AcceptedConfirmation) + AcceptedConfirmation {senderConf = SMPConfirmation {senderKey}} <- + withStore (`getAcceptedConfirmation` connId) + secureQueue c rq senderKey withStore $ \st -> setRcvQueueStatus st rq Secured activateSecuredQueue rq sq Secured -> activateSecuredQueue rq sq @@ -350,17 +353,15 @@ subscribeConnection' c connId = SomeConn _ (SndConnection _ sq) -> do resumeMsgDelivery c connId sq case status (sq :: SndQueue) of - Confirmed -> activateQueueJoining c connId sq (verifyKey sq) =<< resumeInterval + Confirmed -> activateQueueJoining c connId sq =<< resumeInterval Active -> throwError $ CONN SIMPLEX _ -> throwError $ INTERNAL "unexpected queue status" SomeConn _ (RcvConnection _ rq) -> subscribeQueue c rq connId SomeConn _ (ContactConnection _ rq) -> subscribeQueue c rq connId where - verifyKey :: SndQueue -> C.APublicVerifyKey - verifyKey = C.publicKey . signKey activateSecuredQueue :: RcvQueue -> SndQueue -> m () activateSecuredQueue rq sq = do - activateQueueInitiating c connId sq (verifyKey sq) =<< resumeInterval + activateQueueInitiating c connId sq =<< resumeInterval subscribeQueue c rq connId resumeInterval :: m RetryInterval resumeInterval = do @@ -386,15 +387,10 @@ sendMessage' c connId msg = storeSentMsg = do internalTs <- liftIO getCurrentTime withStore $ \st -> do - (internalId, internalSndId, previousMsgHash) <- updateSndIds st connId + (internalId, internalSndId, prevMsgHash) <- updateSndIds st connId let msgBody = - serializeSMPMessage - SMPMessage - { senderMsgId = unSndId internalSndId, - senderTimestamp = internalTs, - previousMsgHash, - agentMessage = A_MSG msg - } + serializeAgentMessage $ + AgentMessage (AHeader (unSndId internalSndId) prevMsgHash) (A_MSG msg) internalHash = C.sha256Hash msgBody msgData = SndMsgData {..} createSndMsg st connId msgData @@ -505,14 +501,8 @@ getSMPServer = sendControlMessage :: AgentMonad m => AgentClient -> SndQueue -> AMessage -> m () sendControlMessage c sq agentMessage = do - senderTimestamp <- liftIO getCurrentTime - sendAgentMessage c sq . serializeSMPMessage $ - SMPMessage - { senderMsgId = 0, - senderTimestamp, - previousMsgHash = "", - agentMessage - } + sendAgentMessage c sq . serializeAgentMessage $ + AgentMessage (AHeader 0 "") agentMessage subscriber :: (MonadUnliftIO m, MonadReader Env m) => AgentClient -> m () subscriber c@AgentClient {msgQ} = forever $ do @@ -530,22 +520,39 @@ processSMPTransmission c@AgentClient {subQ} (srv, rId, cmd) = do _ -> atomically $ writeTBQueue subQ ("", "", ERR $ CONN NOT_FOUND) where processSMP :: SConnType c -> ConnData -> RcvQueue -> m () - processSMP cType ConnData {connId} rq@RcvQueue {rcvDhSecret, status} = + processSMP cType ConnData {connId} rq@RcvQueue {rcvDhSecret, e2ePrivKey, e2eShared, status} = case cmd of - SMP.MSG srvMsgId srvTs msgBody' -> do + SMP.MSG srvMsgId srvTs msgBody' -> handleNotifyAck $ do -- TODO deduplicate with previously received - msgBody <- liftEither . first cryptoError $ C.cbDecrypt rcvDhSecret (C.cbNonce srvMsgId) msgBody' - msg <- decryptAndVerify rq msgBody - let msgHash = C.sha256Hash msg - case parseSMPMessage msg of - Left e -> notify (ERR e) >> sendAck c rq - Right (SMPConfirmation senderKey cInfo) -> smpConfirmation senderKey cInfo >> sendAck c rq - Right SMPMessage {agentMessage, senderMsgId, senderTimestamp, previousMsgHash} -> + msgBody <- agentCbDecrypt rcvDhSecret (C.cbNonce srvMsgId) msgBody' + encMessage@SMP.EncMessage {emHeader = SMP.PubHeader v e2ePubKey} <- + liftEither $ parse SMP.encMessageP (AGENT A_MESSAGE) msgBody + case e2eShared of + Nothing -> do + let e2eDhSecret = C.dh' e2ePubKey e2ePrivKey + (_, agentMessage) <- + decryptAgentMessage e2eDhSecret encMessage case agentMessage of - HELLO verifyKey _ -> helloMsg verifyKey msgBody >> sendAck c rq - REPLY cReq -> replyMsg cReq >> sendAck c rq - A_MSG body -> agentClientMsg previousMsgHash (senderMsgId, senderTimestamp) (srvMsgId, srvTs) body msgHash - A_INV cReq cInfo -> smpInvitation cReq cInfo >> sendAck c rq + AgentConfirmation senderKey connInfo -> do + smpConfirmation SMPConfirmation {senderKey, e2ePubKey, connInfo} + ack + AgentInvitation cReq cInfo -> smpInvitation cReq cInfo >> ack + _ -> prohibited >> ack + Just (e2eSndPubKey, e2eDhSecret) + | e2eSndPubKey /= e2ePubKey -> prohibited >> ack + | otherwise -> do + (msg, agentMessage) <- + decryptAgentMessage e2eDhSecret encMessage + case agentMessage of + AgentMessage AHeader {sndMsgId, prevMsgHash} aMsg -> case aMsg of + HELLO -> helloMsg >> ack + REPLY cReq -> replyMsg cReq >> ack + A_MSG body -> do + -- note that there is no ACK sent here, it is sent with agent's user ACK command + -- TODO add hash to other messages + let msgHash = C.sha256Hash msg + agentClientMsg prevMsgHash sndMsgId (srvMsgId, srvTs) body msgHash + _ -> prohibited >> ack SMP.END -> do removeSubscription c connId logServer "<--" c srv rId "END" @@ -557,60 +564,72 @@ processSMPTransmission c@AgentClient {subQ} (srv, rId, cmd) = do notify :: ACommand 'Agent -> m () notify msg = atomically $ writeTBQueue subQ ("", connId, msg) + handleNotifyAck :: m () -> m () + handleNotifyAck m = m `catchError` \e -> notify (ERR e) >> ack + prohibited :: m () prohibited = notify . ERR $ AGENT A_PROHIBITED - smpConfirmation :: SndPublicVerifyKey -> ConnInfo -> m () - smpConfirmation senderKey cInfo = do + ack :: m () + ack = sendAck c rq + + decryptAgentMessage :: C.DhSecretX25519 -> SMP.EncMessage -> m (ByteString, AgentMessage) + decryptAgentMessage e2eDhSecret SMP.EncMessage {emNonce, emBody} = do + msg <- agentCbDecrypt e2eDhSecret emNonce emBody + agentMessage <- + liftEither $ clientToAgentMsg =<< parse SMP.clientMessageP (AGENT A_MESSAGE) msg + pure (msg, agentMessage) + + smpConfirmation :: SMPConfirmation -> m () + smpConfirmation senderConf@SMPConfirmation {connInfo} = do logServer "<--" c srv rId "MSG " case status of New -> case cType of SCRcv -> do g <- asks idsDrg - let newConfirmation = NewConfirmation {connId, senderKey, senderConnInfo = cInfo} + let newConfirmation = NewConfirmation {connId, senderConf} confId <- withStore $ \st -> createConfirmation st g newConfirmation - notify $ CONF confId cInfo + notify $ CONF confId connInfo SCDuplex -> do - notify $ INFO cInfo - processConfirmation c rq senderKey + notify $ INFO connInfo + processConfirmation c rq senderConf _ -> prohibited _ -> prohibited - helloMsg :: SndPublicVerifyKey -> ByteString -> m () - helloMsg verifyKey msgBody = do + helloMsg :: m () + helloMsg = do logServer "<--" c srv rId "MSG " case status of Active -> prohibited _ -> do - void $ verifyMessage (Just verifyKey) msgBody - withStore $ \st -> setRcvQueueActive st rq verifyKey + withStore $ \st -> setRcvQueueStatus st rq Active case cType of SCDuplex -> notifyConnected c connId _ -> pure () replyMsg :: ConnectionRequest 'CMInvitation -> m () - replyMsg (CRInvitation (ConnReqData _ (qUri :| _) encryptKey)) = do + replyMsg (CRInvitation (ConnReqData _ (qUri :| _) _)) = do logServer "<--" c srv rId "MSG " case cType of SCRcv -> do AcceptedConfirmation {ownConnInfo} <- withStore (`getAcceptedConfirmation` connId) - (sq, senderKey, verifyKey) <- newSndQueue qUri encryptKey + (sq, smpConf) <- newSndQueue qUri ownConnInfo withStore $ \st -> upgradeRcvConnToDuplex st connId sq - confirmQueue c sq senderKey ownConnInfo + confirmQueue c sq smpConf withStore (`removeConfirmations` connId) cfg <- asks config - activateQueueInitiating c connId sq verifyKey $ retryInterval cfg + activateQueueInitiating c connId sq $ retryInterval cfg _ -> prohibited - agentClientMsg :: PrevRcvMsgHash -> (ExternalSndId, ExternalSndTs) -> (BrokerId, BrokerTs) -> MsgBody -> MsgHash -> m () - agentClientMsg externalPrevSndHash sender broker msgBody internalHash = do + agentClientMsg :: PrevRcvMsgHash -> ExternalSndId -> (BrokerId, BrokerTs) -> MsgBody -> MsgHash -> m () + agentClientMsg externalPrevSndHash sndMsgId broker msgBody internalHash = do logServer "<--" c srv rId "MSG " internalTs <- liftIO getCurrentTime (internalId, internalRcvId, prevExtSndId, prevRcvMsgHash) <- withStore (`updateRcvIds` connId) - let integrity = checkMsgIntegrity prevExtSndId (fst sender) prevRcvMsgHash externalPrevSndHash + let integrity = checkMsgIntegrity prevExtSndId sndMsgId prevRcvMsgHash externalPrevSndHash recipient = (unId internalId, internalTs) - msgMeta = MsgMeta {integrity, recipient, sender, broker} - rcvMsg = RcvMsgData {..} + msgMeta = MsgMeta {integrity, recipient, broker, sndMsgId} + rcvMsg = RcvMsgData {msgMeta, msgBody, internalRcvId, internalHash, externalPrevSndHash} withStore $ \st -> createRcvMsg st connId rcvMsg notify $ MSG msgMeta msgBody @@ -634,24 +653,24 @@ processSMPTransmission c@AgentClient {subQ} (srv, rId, cmd) = do | internalPrevMsgHash /= receivedPrevMsgHash = MsgError MsgBadHash | otherwise = MsgError MsgDuplicate -- this case is not possible -confirmQueue :: AgentMonad m => AgentClient -> SndQueue -> SndPublicVerifyKey -> ConnInfo -> m () -confirmQueue c sq senderKey cInfo = do - sendConfirmation c sq senderKey cInfo +confirmQueue :: AgentMonad m => AgentClient -> SndQueue -> SMPConfirmation -> m () +confirmQueue c sq smpConf = do + sendConfirmation c sq smpConf withStore $ \st -> setSndQueueStatus st sq Confirmed -activateQueueInitiating :: AgentMonad m => AgentClient -> ConnId -> SndQueue -> C.APublicVerifyKey -> RetryInterval -> m () -activateQueueInitiating c connId sq verifyKey retryInterval = - activateQueue c connId sq verifyKey retryInterval $ notifyConnected c connId +activateQueueInitiating :: AgentMonad m => AgentClient -> ConnId -> SndQueue -> RetryInterval -> m () +activateQueueInitiating c connId sq retryInterval = + activateQueue c connId sq retryInterval $ notifyConnected c connId -activateQueue :: forall m. AgentMonad m => AgentClient -> ConnId -> SndQueue -> C.APublicVerifyKey -> RetryInterval -> m () -> m () -activateQueue c connId sq verifyKey retryInterval afterActivation = +activateQueue :: forall m. AgentMonad m => AgentClient -> ConnId -> SndQueue -> RetryInterval -> m () -> m () +activateQueue c connId sq retryInterval afterActivation = getActivation c connId >>= \case Nothing -> async runActivation >>= addActivation c connId Just _ -> pure () where runActivation :: m () runActivation = do - sendHello c sq verifyKey retryInterval + sendHello c sq retryInterval withStore $ \st -> setSndQueueStatus st sq Active removeActivation c connId afterActivation @@ -659,29 +678,27 @@ activateQueue c connId sq verifyKey retryInterval afterActivation = notifyConnected :: AgentMonad m => AgentClient -> ConnId -> m () notifyConnected c connId = atomically $ writeTBQueue (subQ c) ("", connId, CON) -newSndQueue :: - (MonadUnliftIO m, MonadReader Env m) => SMPQueueUri -> C.APublicEncryptKey -> m (SndQueue, SndPublicVerifyKey, C.APublicVerifyKey) -newSndQueue qUri encryptKey = +newSndQueue :: (MonadUnliftIO m, MonadReader Env m) => SMPQueueUri -> ConnInfo -> m (SndQueue, SMPConfirmation) +newSndQueue qUri cInfo = asks (cmdSignAlg . config) >>= \case - C.SignAlg a -> newSndQueue_ a qUri encryptKey + C.SignAlg a -> newSndQueue_ a qUri cInfo newSndQueue_ :: - (C.SignatureAlgorithm a, C.AlgorithmI a, MonadUnliftIO m, MonadReader Env m) => + (C.SignatureAlgorithm a, C.AlgorithmI a, MonadUnliftIO m) => C.SAlgorithm a -> SMPQueueUri -> - C.APublicEncryptKey -> - m (SndQueue, SndPublicVerifyKey, C.APublicVerifyKey) -newSndQueue_ a (SMPQueueUri smpServer senderId) encryptKey = do - size <- asks $ rsaKeySize . config - (senderKey, sndPrivateKey) <- liftIO $ C.generateSignatureKeyPair size a - (verifyKey, signKey) <- liftIO $ C.generateSignatureKeyPair size C.SRSA + ConnInfo -> + m (SndQueue, SMPConfirmation) +newSndQueue_ a (SMPQueueUri smpServer senderId rcvE2ePubDhKey) cInfo = do + (senderKey, sndPrivateKey) <- liftIO $ C.generateSignatureKeyPair a + (e2ePubKey, e2ePrivKey) <- liftIO C.generateKeyPair' let sndQueue = SndQueue { server = smpServer, sndId = senderId, sndPrivateKey, - encryptKey, - signKey, + e2ePubKey, + e2eDhSecret = C.dh' rcvE2ePubDhKey e2ePrivKey, status = New } - return (sndQueue, senderKey, verifyKey) + pure (sndQueue, SMPConfirmation senderKey e2ePubKey cInfo) diff --git a/src/Simplex/Messaging/Agent/Client.hs b/src/Simplex/Messaging/Agent/Client.hs index 8e19be712..20defad38 100644 --- a/src/Simplex/Messaging/Agent/Client.hs +++ b/src/Simplex/Messaging/Agent/Client.hs @@ -22,14 +22,12 @@ module Simplex.Messaging.Agent.Client sendHello, secureQueue, sendAgentMessage, - decryptAndVerify, - verifyMessage, + agentCbDecrypt, sendAck, suspendQueue, deleteQueue, logServer, removeSubscription, - cryptoError, addActivation, getActivation, removeActivation, @@ -43,6 +41,7 @@ import Control.Monad.Except import Control.Monad.IO.Unlift import Control.Monad.Reader import Control.Monad.Trans.Except +import Data.Bifunctor (first) import Data.ByteString.Base64 import Data.ByteString.Char8 (ByteString) import qualified Data.ByteString.Char8 as B @@ -52,7 +51,6 @@ import Data.Maybe (isNothing) import Data.Set (Set) import qualified Data.Set as S import Data.Text.Encoding -import Data.Time.Clock import Simplex.Messaging.Agent.Env.SQLite import Simplex.Messaging.Agent.Protocol import Simplex.Messaging.Agent.RetryInterval @@ -60,6 +58,7 @@ import Simplex.Messaging.Agent.Store import Simplex.Messaging.Client import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Protocol (ErrorType (AUTH), MsgBody, QueueId, QueueIdsKeys (..), SndPublicVerifyKey) +import qualified Simplex.Messaging.Protocol as SMP import Simplex.Messaging.Util (bshow, liftEitherError, liftError) import UnliftIO.Exception (IOException) import qualified UnliftIO.Exception as E @@ -224,7 +223,7 @@ smpClientError = \case SMPTransportError e -> BROKER $ TRANSPORT e e -> INTERNAL $ show e -newRcvQueue :: AgentMonad m => AgentClient -> SMPServer -> m (RcvQueue, SMPQueueUri, C.APublicEncryptKey) +newRcvQueue :: AgentMonad m => AgentClient -> SMPServer -> m (RcvQueue, SMPQueueUri) newRcvQueue c srv = asks (cmdSignAlg . config) >>= \case C.SignAlg a -> newRcvQueue_ a c srv @@ -234,28 +233,27 @@ newRcvQueue_ :: C.SAlgorithm a -> AgentClient -> SMPServer -> - m (RcvQueue, SMPQueueUri, C.APublicEncryptKey) + m (RcvQueue, SMPQueueUri) newRcvQueue_ a c srv = do - size <- asks $ rsaKeySize . config - (recipientKey, rcvPrivateKey) <- liftIO $ C.generateSignatureKeyPair size a - (dhKey, privDhKey) <- liftIO $ C.generateKeyPair' 0 + (recipientKey, rcvPrivateKey) <- liftIO $ C.generateSignatureKeyPair a + (dhKey, privDhKey) <- liftIO C.generateKeyPair' + (e2eDhKey, e2ePrivKey) <- liftIO C.generateKeyPair' logServer "-->" c srv "" "NEW" - QIK {rcvId, sndId, rcvPublicDHKey} <- + QIK {rcvId, sndId, rcvPublicDhKey} <- withSMP c srv $ \smp -> createSMPQueue smp rcvPrivateKey recipientKey dhKey logServer "<--" c srv "" $ B.unwords ["IDS", logSecret rcvId, logSecret sndId] - (encryptKey, decryptKey) <- liftIO $ C.generateEncryptionKeyPair size C.SRSA let rq = RcvQueue { server = srv, rcvId, rcvPrivateKey, - rcvDhSecret = C.dh' rcvPublicDHKey privDhKey, + rcvDhSecret = C.dh' rcvPublicDhKey privDhKey, + e2ePrivKey, + e2eShared = Nothing, sndId = Just sndId, - decryptKey, - verifyKey = Nothing, status = New } - pure (rq, SMPQueueUri srv sndId, encryptKey) + pure (rq, SMPQueueUri srv sndId e2eDhKey) subscribeQueue :: AgentMonad m => AgentClient -> RcvQueue -> ConnId -> m () subscribeQueue c rq@RcvQueue {server, rcvPrivateKey, rcvId} connId = do @@ -304,51 +302,41 @@ showServer srv = B.pack $ host srv <> maybe "" (":" <>) (port srv) logSecret :: ByteString -> ByteString logSecret bs = encode $ B.take 3 bs -sendConfirmation :: forall m. AgentMonad m => AgentClient -> SndQueue -> SndPublicVerifyKey -> ConnInfo -> m () -sendConfirmation c sq@SndQueue {server, sndId} senderKey cInfo = +sendConfirmation :: forall m. AgentMonad m => AgentClient -> SndQueue -> SMPConfirmation -> m () +sendConfirmation c sq@SndQueue {server, sndId} SMPConfirmation {senderKey, connInfo} = withLogSMP_ c server sndId "SEND " $ \smp -> do - msg <- mkConfirmation smp + msg <- mkConfirmation liftSMP $ sendSMPMessage smp Nothing sndId msg where - mkConfirmation :: SMPClient -> m MsgBody - mkConfirmation smp = encryptAndSign smp sq . serializeSMPMessage $ SMPConfirmation senderKey cInfo + mkConfirmation :: m MsgBody + mkConfirmation = + agentCbEncrypt sq . serializeAgentMessage $ + AgentConfirmation senderKey connInfo -sendHello :: forall m. AgentMonad m => AgentClient -> SndQueue -> C.APublicVerifyKey -> RetryInterval -> m () -sendHello c sq@SndQueue {server, sndId, sndPrivateKey} verifyKey ri = +sendHello :: forall m. AgentMonad m => AgentClient -> SndQueue -> RetryInterval -> m () +sendHello c sq@SndQueue {server, sndId, sndPrivateKey} ri = withLogSMP_ c server sndId "SEND (retrying)" $ \smp -> do - msg <- mkHello smp $ AckMode On + msg <- mkHello liftSMP . withRetryInterval ri $ \loop -> sendSMPMessage smp (Just sndPrivateKey) sndId msg `catchE` \case SMPServerError AUTH -> loop e -> throwE e where - mkHello :: SMPClient -> AckMode -> m ByteString - mkHello smp ackMode = do - senderTimestamp <- liftIO getCurrentTime - encryptAndSign smp sq . serializeSMPMessage $ - SMPMessage - { senderMsgId = 0, - senderTimestamp, - previousMsgHash = "", - agentMessage = HELLO verifyKey ackMode - } + mkHello :: m ByteString + mkHello = do + agentCbEncrypt sq . serializeAgentMessage $ + AgentMessage (AHeader 0 "") HELLO -sendInvitation :: forall m. AgentMonad m => AgentClient -> SMPQueueUri -> C.APublicEncryptKey -> ConnectionRequest 'CMInvitation -> ConnInfo -> m () -sendInvitation c SMPQueueUri {smpServer, senderId} encryptKey cReq connInfo = do +sendInvitation :: forall m. AgentMonad m => AgentClient -> SMPQueueUri -> ConnectionRequest 'CMInvitation -> ConnInfo -> m () +sendInvitation c SMPQueueUri {smpServer, senderId, dhPublicKey} cReq connInfo = do withLogSMP_ c smpServer senderId "SEND " $ \smp -> do - msg <- mkInvitation smp + msg <- mkInvitation liftSMP $ sendSMPMessage smp Nothing senderId msg where - mkInvitation :: SMPClient -> m ByteString - mkInvitation smp = do - senderTimestamp <- liftIO getCurrentTime - encryptUnsigned smp encryptKey . serializeSMPMessage $ - SMPMessage - { senderMsgId = 0, - senderTimestamp, - previousMsgHash = "", - agentMessage = A_INV cReq connInfo - } + mkInvitation :: m ByteString + mkInvitation = + agentCbEncryptOnce dhPublicKey . serializeAgentMessage $ + AgentInvitation cReq connInfo secureQueue :: AgentMonad m => AgentClient -> RcvQueue -> SndPublicVerifyKey -> m () secureQueue c RcvQueue {server, rcvId, rcvPrivateKey} senderKey = @@ -373,49 +361,40 @@ deleteQueue c RcvQueue {server, rcvId, rcvPrivateKey} = sendAgentMessage :: AgentMonad m => AgentClient -> SndQueue -> ByteString -> m () sendAgentMessage c sq@SndQueue {server, sndId, sndPrivateKey} msg = withLogSMP_ c server sndId "SEND " $ \smp -> do - msg' <- encryptAndSign smp sq msg + msg' <- agentCbEncrypt sq msg liftSMP $ sendSMPMessage smp (Just sndPrivateKey) sndId msg' -encryptAndSign :: AgentMonad m => SMPClient -> SndQueue -> ByteString -> m ByteString -encryptAndSign smp SndQueue {encryptKey, signKey} msg = do - paddedSize <- asks $ (blockSize smp -) . reservedMsgSize - liftError cryptoError $ do - enc <- C.encrypt encryptKey paddedSize msg - sig <- C.sign signKey enc - pure $ C.signatureBytes sig <> enc +agentCbEncrypt :: AgentMonad m => SndQueue -> ByteString -> m ByteString +agentCbEncrypt SndQueue {e2ePubKey, e2eDhSecret} msg = do + emNonce <- liftIO C.randomCbNonce + emBody <- + liftEither . first cryptoError $ + C.cbEncrypt e2eDhSecret emNonce msg SMP.e2eEncMessageLength + -- TODO per-queue client version + let emHeader = SMP.PubHeader SMP.clientVersion e2ePubKey + pure $ SMP.serializeEncMessage SMP.EncMessage {emHeader, emNonce, emBody} -decryptAndVerify :: AgentMonad m => RcvQueue -> MsgBody -> m ByteString -decryptAndVerify RcvQueue {decryptKey, verifyKey} msg = - verifyMessage verifyKey msg - >>= liftError cryptoError . C.decrypt decryptKey +agentCbEncryptOnce :: AgentMonad m => C.PublicKeyX25519 -> ByteString -> m ByteString +agentCbEncryptOnce dhRcvPubKey msg = do + (dhSndPubKey, dhSndPrivKey) <- liftIO C.generateKeyPair' + let e2eDhSecret = C.dh' dhRcvPubKey dhSndPrivKey + emNonce <- liftIO C.randomCbNonce + emBody <- + liftEither . first cryptoError $ + C.cbEncrypt e2eDhSecret emNonce msg SMP.e2eEncMessageLength + -- TODO per-queue client version + let emHeader = SMP.PubHeader SMP.clientVersion dhSndPubKey + pure $ SMP.serializeEncMessage SMP.EncMessage {emHeader, emNonce, emBody} -encryptUnsigned :: AgentMonad m => SMPClient -> C.APublicEncryptKey -> ByteString -> m ByteString -encryptUnsigned smp encryptKey msg = do - paddedSize <- asks $ (blockSize smp -) . reservedMsgSize - size <- asks $ rsaKeySize . config - liftError cryptoError $ do - enc <- C.encrypt encryptKey paddedSize msg - let sig = B.replicate size ' ' - pure $ sig <> enc - -verifyMessage :: AgentMonad m => Maybe C.APublicVerifyKey -> ByteString -> m ByteString -verifyMessage verifyKey msg = do - sigSize <- asks $ rsaKeySize . config - let (s, enc) = B.splitAt sigSize msg - case verifyKey of - Nothing -> pure enc - Just k -> - case C.decodeSignature $ B.take (C.signatureSize k) s of - Left _ -> throwError $ AGENT A_SIGNATURE - Right sig -> - if C.verify k sig enc - then pure enc - else throwError $ AGENT A_SIGNATURE +agentCbDecrypt :: AgentMonad m => C.DhSecretX25519 -> C.CbNonce -> ByteString -> m ByteString +agentCbDecrypt dhSecret nonce msg = + liftEither . first cryptoError $ + C.cbDecrypt dhSecret nonce msg cryptoError :: C.CryptoError -> AgentErrorType cryptoError = \case C.CryptoLargeMsgError -> CMD LARGE - C.RSADecryptError _ -> AGENT A_ENCRYPTION C.CryptoHeaderError _ -> AGENT A_ENCRYPTION C.AESDecryptError -> AGENT A_ENCRYPTION + C.CBDecryptError -> AGENT A_ENCRYPTION e -> INTERNAL $ show e diff --git a/src/Simplex/Messaging/Agent/Env/SQLite.hs b/src/Simplex/Messaging/Agent/Env/SQLite.hs index 55dd48172..679fe8dac 100644 --- a/src/Simplex/Messaging/Agent/Env/SQLite.hs +++ b/src/Simplex/Messaging/Agent/Env/SQLite.hs @@ -23,7 +23,6 @@ import UnliftIO.STM data AgentConfig = AgentConfig { tcpPort :: ServiceName, smpServers :: NonEmpty SMPServer, - rsaKeySize :: Int, cmdSignAlg :: C.SignAlg, connIdBytes :: Int, tbqSize :: Natural, @@ -45,7 +44,6 @@ defaultAgentConfig = AgentConfig { tcpPort = "5224", smpServers = undefined, - rsaKeySize = 2048 `div` 8, cmdSignAlg = C.SignAlg C.SEd448, connIdBytes = 12, tbqSize = 16, @@ -75,7 +73,6 @@ data Env = Env store :: SQLiteStore, idsDrg :: TVar ChaChaDRG, clientCounter :: TVar Int, - reservedMsgSize :: Int, randomServer :: TVar StdGen } @@ -85,10 +82,4 @@ newSMPAgentEnv cfg = do store <- liftIO $ createSQLiteStore (dbFile cfg) (dbPoolSize cfg) Migrations.app clientCounter <- newTVarIO 0 randomServer <- newTVarIO =<< liftIO newStdGen - return Env {config = cfg, store, idsDrg, clientCounter, reservedMsgSize, randomServer} - where - -- 1st rsaKeySize is used by the RSA signature in each command, - -- 2nd - by encrypted message body header - -- 3rd - by message signature - -- smpCommandSize - is the estimated max size for SMP command, queueId, corrId - reservedMsgSize = 3 * rsaKeySize cfg + smpCommandSize (smpCfg cfg) + return Env {config = cfg, store, idsDrg, clientCounter, randomServer} diff --git a/src/Simplex/Messaging/Agent/Protocol.hs b/src/Simplex/Messaging/Agent/Protocol.hs index 3ca65be9d..bea709692 100644 --- a/src/Simplex/Messaging/Agent/Protocol.hs +++ b/src/Simplex/Messaging/Agent/Protocol.hs @@ -1,6 +1,7 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE LambdaCase #-} @@ -35,7 +36,9 @@ module Simplex.Messaging.Agent.Protocol SAParty (..), MsgHash, MsgMeta (..), - SMPMessage (..), + SMPConfirmation (..), + AgentMessage (..), + AHeader (..), AMessage (..), SMPServer (..), SMPQueueUri (..), @@ -49,6 +52,7 @@ module Simplex.Messaging.Agent.Protocol AConnectionRequest (..), ConnReqData (..), ConnReqScheme (..), + ConnectionEncryption (..), simplexChat, AgentErrorType (..), CommandErrorType (..), @@ -61,8 +65,6 @@ module Simplex.Messaging.Agent.Protocol ConnId, ConfirmationId, InvitationId, - AckMode (..), - OnOff (..), MsgIntegrity (..), MsgErrorType (..), QueueStatus (..), @@ -71,11 +73,10 @@ module Simplex.Messaging.Agent.Protocol -- * Parse and serialize serializeCommand, - serializeSMPMessage, + clientToAgentMsg, + serializeAgentMessage, serializeMsgIntegrity, - serializeServer, serializeSMPQueueUri, - reservedServerKey, -- TODO remove serializeConnMode, serializeConnMode', connMode, @@ -84,7 +85,6 @@ module Simplex.Messaging.Agent.Protocol serializeConnReq', serializeAgentError, commandP, - parseSMPMessage, smpServerP, smpQueueUriP, connModeT, @@ -92,7 +92,6 @@ module Simplex.Messaging.Agent.Protocol connReqP', msgIntegrityP, agentErrorTypeP, - agentMessageP, -- * TCP transport functions tPut, @@ -104,7 +103,6 @@ where import Control.Applicative (optional, (<|>)) import Control.Monad.IO.Class -import qualified Crypto.PubKey.RSA as R import Data.Attoparsec.ByteString.Char8 (Parser) import qualified Data.Attoparsec.ByteString.Char8 as A import Data.ByteString.Base64 @@ -130,10 +128,13 @@ import Network.Socket (HostName, ServiceName) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Parsers import Simplex.Messaging.Protocol - ( ErrorType, + ( ClientMessage (..), + ErrorType, MsgBody, MsgId, + PrivHeader (..), SndPublicVerifyKey, + serializeClientMessage, ) import qualified Simplex.Messaging.Protocol as SMP import Simplex.Messaging.Transport (Transport (..), TransportError, serializeTransportError, transportErrorP) @@ -261,93 +262,87 @@ data MsgMeta = MsgMeta { integrity :: MsgIntegrity, recipient :: (AgentMsgId, UTCTime), broker :: (MsgId, UTCTime), - sender :: (AgentMsgId, UTCTime) + sndMsgId :: AgentMsgId } deriving (Eq, Show) --- | SMP message formats. -data SMPMessage - = -- | SMP confirmation - -- (see ) - SMPConfirmation - { -- | sender's public key to use for authentication of sender's commands at the recepient's server - senderKey :: SndPublicVerifyKey, - -- | sender's information to be associated with the connection, e.g. sender's profile information - connInfo :: ConnInfo - } - | -- | Agent message header and envelope for client messages - -- (see ) - SMPMessage - { -- | sequential ID assigned by the sending agent - senderMsgId :: AgentMsgId, - -- | timestamp from the sending agent - senderTimestamp :: SenderTimestamp, - -- | digest of the previous message - previousMsgHash :: MsgHash, - -- | messages sent between agents once queue is secured - agentMessage :: AMessage - } +data SMPConfirmation = SMPConfirmation + { -- | sender's public key to use for authentication of sender's commands at the recepient's server + senderKey :: SndPublicVerifyKey, + -- | sender's DH public key for simple per-queue e2e encryption + e2ePubKey :: C.PublicKeyX25519, + -- | sender's information to be associated with the connection, e.g. sender's profile information + connInfo :: ConnInfo + } deriving (Show) +-- SMP agent message formats +data AgentMessage + = AgentConfirmation C.APublicVerifyKey ConnInfo -- TODO add double ratchet E2E settings + | AgentInvitation (ConnectionRequest CMInvitation) ConnInfo + | AgentMessage AHeader AMessage + +data AHeader = AHeader + { -- | sequential ID assigned by the sending agent + sndMsgId :: AgentMsgId, + -- | digest of the previous message + prevMsgHash :: MsgHash + } + +serializeAHeader :: AHeader -> ByteString +serializeAHeader AHeader {sndMsgId, prevMsgHash} = + bshow sndMsgId <> " " <> encode prevMsgHash <> "\n" + +emptyAHeader :: ByteString +emptyAHeader = "\n" + +aHeaderP :: Parser AHeader +aHeaderP = AHeader <$> A.decimal <* A.space <*> (base64P <|> pure "") <* A.endOfLine + +emptyAHeaderP :: Parser () +emptyAHeaderP = A.endOfLine $> () + -- | Messages sent between SMP agents once SMP queue is secured. -- -- https://github.com/simplex-chat/simplexmq/blob/master/protocol/agent-protocol.md#messages-between-smp-agents -data AMessage where - -- | the first message in the queue to validate it is secured - HELLO :: C.APublicVerifyKey -> AckMode -> AMessage - -- | reply queue information - REPLY :: ConnectionRequest CMInvitation -> AMessage - -- | agent envelope for the client message - A_MSG :: MsgBody -> AMessage - -- | connection request with the invitation to connect - A_INV :: ConnectionRequest CMInvitation -> ConnInfo -> AMessage +data AMessage + = -- | the first message in the queue to validate it is secured + HELLO + | -- | reply queue information + REPLY (ConnectionRequest CMInvitation) + | -- | agent envelope for the client message + A_MSG MsgBody deriving (Show) --- | Parse SMP message. -parseSMPMessage :: ByteString -> Either AgentErrorType SMPMessage -parseSMPMessage = parse (smpMessageP <* A.endOfLine) $ AGENT A_MESSAGE +serializeAgentMessage :: AgentMessage -> ByteString +serializeAgentMessage = serializeClientMessage . agentToClientMsg + +agentToClientMsg :: AgentMessage -> ClientMessage +agentToClientMsg = \case + AgentConfirmation senderKey cInfo -> + ClientMessage (PHConfirmation senderKey) $ emptyAHeader <> cInfo + AgentInvitation cReq cInfo -> + ClientMessage PHEmpty $ emptyAHeader <> serializeConnReq' cReq <> "\n" <> cInfo + AgentMessage header aMsg -> + ClientMessage PHEmpty $ serializeAHeader header <> serializeAMessage aMsg + +clientToAgentMsg :: ClientMessage -> Either AgentErrorType AgentMessage +clientToAgentMsg (ClientMessage header body) = parse parser (AGENT A_MESSAGE) body where - smpMessageP :: Parser SMPMessage - smpMessageP = A.endOfLine *> smpClientMessageP <|> smpConfirmationP + parser = case header of + PHConfirmation senderKey -> AgentConfirmation senderKey <$> (emptyAHeaderP *> A.takeByteString) + PHEmpty -> invitationP <|> messageP + invitationP = AgentInvitation <$> (emptyAHeaderP *> connReqP' <* A.endOfLine) <*> A.takeByteString + messageP = AgentMessage <$> aHeaderP <*> aMessageP - smpConfirmationP :: Parser SMPMessage - smpConfirmationP = "KEY " *> (SMPConfirmation <$> C.strKeyP <* A.endOfLine <* A.endOfLine <*> binaryBodyP <* A.endOfLine) - - smpClientMessageP :: Parser SMPMessage - smpClientMessageP = - SMPMessage - <$> A.decimal <* A.space - <*> tsISO8601P <* A.space - -- TODO previous message hash should become mandatory when we support HELLO and REPLY - -- (for HELLO it would be the hash of SMPConfirmation) - <*> (base64P <|> pure "") <* A.endOfLine - <*> agentMessageP - --- | Serialize SMP message. -serializeSMPMessage :: SMPMessage -> ByteString -serializeSMPMessage = \case - SMPConfirmation sKey cInfo -> smpMessage ("KEY " <> C.serializeKey sKey) "" (serializeBinary cInfo) <> "\n" - SMPMessage {senderMsgId, senderTimestamp, previousMsgHash, agentMessage} -> - let header = messageHeader senderMsgId senderTimestamp previousMsgHash - body = serializeAgentMessage agentMessage - in smpMessage "" header body - where - messageHeader msgId ts prevMsgHash = - B.unwords [bshow msgId, B.pack $ formatISO8601Millis ts, encode prevMsgHash] - smpMessage smpHeader aHeader aBody = B.intercalate "\n" [smpHeader, aHeader, aBody, ""] - -agentMessageP :: Parser AMessage -agentMessageP = - "HELLO " *> hello +aMessageP :: Parser AMessage +aMessageP = + "HELLO" $> HELLO <|> "REPLY " *> reply <|> "MSG " *> a_msg - <|> "INV " *> a_inv where - hello = HELLO <$> C.strKeyP <*> ackMode reply = REPLY <$> connReqP' - a_msg = A_MSG <$> binaryBodyP <* A.endOfLine - a_inv = A_INV <$> connReqP' <* A.space <*> binaryBodyP <* A.endOfLine - ackMode = AckMode <$> (" NO_ACK" $> Off <|> pure On) + a_msg = A_MSG <$> A.takeByteString -- | SMP server location parser. smpServerP :: Parser SMPServer @@ -357,25 +352,21 @@ smpServerP = SMPServer <$> server <*> optional port <*> kHash port = A.char ':' *> (B.unpack <$> A.takeWhile1 A.isDigit) kHash = Just . C.KeyHash <$> (A.char '#' *> base64P) -serializeAgentMessage :: AMessage -> ByteString -serializeAgentMessage = \case - HELLO verifyKey ackMode -> "HELLO " <> C.serializeKey verifyKey <> if ackMode == AckMode Off then " NO_ACK" else "" +serializeAMessage :: AMessage -> ByteString +serializeAMessage = \case + HELLO -> "HELLO" REPLY cReq -> "REPLY " <> serializeConnReq' cReq - A_MSG body -> "MSG " <> serializeBinary body <> "\n" - A_INV cReq cInfo -> B.unwords ["INV", serializeConnReq' cReq, serializeBinary cInfo] <> "\n" + A_MSG body -> "MSG " <> body -- | Serialize SMP queue information that is sent out-of-band. serializeSMPQueueUri :: SMPQueueUri -> ByteString -serializeSMPQueueUri (SMPQueueUri srv qId) = - serializeServerUri srv <> "/" <> U.encode qId <> "#" +serializeSMPQueueUri (SMPQueueUri srv qId dhKey) = + serializeServerUri srv <> "/" <> U.encode qId <> "#" <> C.serializePubKeyUri' dhKey -- | SMP queue information parser. smpQueueUriP :: Parser SMPQueueUri smpQueueUriP = - SMPQueueUri <$> smpServerUriP <* "/" <*> base64UriP <* optional "#" - -reservedServerKey :: C.APublicVerifyKey -reservedServerKey = C.APublicVerifyKey C.SRSA (C.PublicKeyRSA $ R.PublicKey 1 0 0) + SMPQueueUri <$> smpServerUriP <* A.char '/' <*> base64UriP <* A.char '#' <*> C.strPubKeyUriP serializeConnReq :: AConnectionRequest -> ByteString serializeConnReq (ACR _ cr) = serializeConnReq' cr @@ -385,7 +376,7 @@ serializeConnReq' = \case CRInvitation crData -> serialize CMInvitation crData CRContact crData -> serialize CMContact crData where - serialize crMode ConnReqData {crScheme, crSmpQueues, crEncryptKey} = + serialize crMode ConnReqData {crScheme, crSmpQueues, crEncryption = _} = sch <> "/" <> m <> "#/" <> queryStr where sch = case crScheme of @@ -394,9 +385,8 @@ serializeConnReq' = \case m = case crMode of CMInvitation -> "invitation" CMContact -> "contact" - queryStr = renderSimpleQuery True [("smp", queues), ("e2e", key)] + queryStr = renderSimpleQuery True [("smp", queues), ("e2e", "")] queues = B.intercalate "," . map serializeSMPQueueUri $ L.toList crSmpQueues - key = C.serializeKeyUri crEncryptKey connReqP' :: forall m. ConnectionModeI m => Parser (ConnectionRequest m) connReqP' = do @@ -411,8 +401,8 @@ connReqP = do crMode <- "/" *> mode <* "#/?" query <- parseSimpleQuery <$> A.takeTill (\c -> c == ' ' || c == '\n') crSmpQueues <- paramP "smp" smpQueues query - crEncryptKey <- paramP "e2e" C.strKeyUriP query - let cReq = ConnReqData {crScheme, crSmpQueues, crEncryptKey} + let crEncryption = ConnectionEncryption + cReq = ConnReqData {crScheme, crSmpQueues, crEncryption} pure $ case crMode of CMInvitation -> ACR SCMInvitation $ CRInvitation cReq CMContact -> ACR SCMContact $ CRContact cReq @@ -429,11 +419,7 @@ connReqP = do =<< (smpQueue `A.sepBy1'` A.char ',') smpQueue = parseAll smpQueueUriP <$?> A.takeTill (== ',') --- | Serialize SMP server location. -serializeServer :: SMPServer -> ByteString -serializeServer SMPServer {host, port, keyHash} = - B.pack $ host <> maybe "" (':' :) port <> maybe "" (('#' :) . B.unpack . encode . C.unKeyHash) keyHash - +-- | Serialize SMP server URI. serializeServerUri :: SMPServer -> ByteString serializeServerUri SMPServer {host, port, keyHash} = "smp://" <> kh <> B.pack host <> p where @@ -486,18 +472,13 @@ type ConfirmationId = ByteString type InvitationId = ByteString --- | Connection modes. -data OnOff = On | Off deriving (Eq, Show, Read) - --- | Message acknowledgement mode of the connection. -newtype AckMode = AckMode OnOff deriving (Eq, Show) - -- | SMP queue information sent out-of-band. -- -- https://github.com/simplex-chat/simplexmq/blob/master/protocol/simplex-messaging.md#out-of-band-messages data SMPQueueUri = SMPQueueUri { smpServer :: SMPServer, - senderId :: SMP.SenderId + senderId :: SMP.SenderId, + dhPublicKey :: C.PublicKeyX25519 } deriving (Eq, Show) @@ -521,13 +502,17 @@ deriving instance Show AConnectionRequest data ConnReqData = ConnReqData { crScheme :: ConnReqScheme, crSmpQueues :: L.NonEmpty SMPQueueUri, - crEncryptKey :: C.APublicEncryptKey + crEncryption :: ConnectionEncryption } deriving (Eq, Show) data ConnReqScheme = CRSSimplex | CRSAppServer HostName (Maybe ServiceName) deriving (Eq, Show) +-- TODO this is a stub for double ratchet E2E encryption parameters (2 public DH keys) +data ConnectionEncryption = ConnectionEncryption + deriving (Eq, Show) + simplexChat :: ConnReqScheme simplexChat = CRSAppServer "simplex.chat" Nothing @@ -549,8 +534,6 @@ data QueueStatus type AgentMsgId = Int64 -type SenderTimestamp = UTCTime - -- | Result of received message integrity validation. data MsgIntegrity = MsgOk | MsgError MsgErrorType deriving (Eq, Show) @@ -615,14 +598,12 @@ data BrokerErrorType -- | Errors of another SMP agent. data SMPAgentError - = -- | possibly should include bytestring that failed to parse + = -- | client or agent message that failed to parse A_MESSAGE - | -- | possibly should include the prohibited SMP/agent message + | -- | prohibited SMP/agent message A_PROHIBITED - | -- | cannot RSA/AES-decrypt or parse decrypted header + | -- | cannot decrypt message A_ENCRYPTION - | -- | invalid signature - A_SIGNATURE deriving (Eq, Generic, Read, Show, Exception) instance Arbitrary AgentErrorType where arbitrary = genericArbitraryU @@ -682,8 +663,8 @@ commandP = integrity <- msgIntegrityP recipient <- " R=" *> partyMeta A.decimal broker <- " B=" *> partyMeta base64P - sender <- " S=" *> partyMeta A.decimal - pure MsgMeta {integrity, recipient, broker, sender} + sndMsgId <- " S=" *> A.decimal + pure MsgMeta {integrity, recipient, broker, sndMsgId} partyMeta idParser = (,) <$> idParser <* "," <*> tsISO8601P agentError = ACmd SAgent . ERR <$> agentErrorTypeP @@ -731,12 +712,12 @@ serializeCommand = \case showTs :: UTCTime -> ByteString showTs = B.pack . formatISO8601Millis serializeMsgMeta :: MsgMeta -> ByteString - serializeMsgMeta MsgMeta {integrity, recipient = (rmId, rTs), broker = (bmId, bTs), sender = (smId, sTs)} = + serializeMsgMeta MsgMeta {integrity, recipient = (rmId, rTs), broker = (bmId, bTs), sndMsgId} = B.unwords [ serializeMsgIntegrity integrity, "R=" <> bshow rmId <> "," <> showTs rTs, "B=" <> encode bmId <> "," <> showTs bTs, - "S=" <> bshow smId <> "," <> showTs sTs + "S=" <> bshow sndMsgId ] -- | Serialize message integrity validation result. @@ -768,11 +749,6 @@ serializeAgentError = \case BROKER (TRANSPORT e) -> "BROKER TRANSPORT " <> serializeTransportError e e -> bshow e -binaryBodyP :: Parser ByteString -binaryBodyP = do - size :: Int <- A.decimal <* A.endOfLine - A.take size - serializeBinary :: ByteString -> ByteString serializeBinary body = bshow (B.length body) <> "\n" <> body diff --git a/src/Simplex/Messaging/Agent/Store.hs b/src/Simplex/Messaging/Agent/Store.hs index b4bb62592..99a9cd8c8 100644 --- a/src/Simplex/Messaging/Agent/Store.hs +++ b/src/Simplex/Messaging/Agent/Store.hs @@ -25,7 +25,6 @@ import Simplex.Messaging.Protocol RcvDhSecret, RcvPrivateSignKey, SndPrivateSignKey, - SndPublicVerifyKey, ) import qualified Simplex.Messaging.Protocol as SMP @@ -43,7 +42,7 @@ class Monad m => MonadAgentStore s m where upgradeRcvConnToDuplex :: s -> ConnId -> SndQueue -> m () upgradeSndConnToDuplex :: s -> ConnId -> RcvQueue -> m () setRcvQueueStatus :: s -> RcvQueue -> QueueStatus -> m () - setRcvQueueActive :: s -> RcvQueue -> C.APublicVerifyKey -> m () + setRcvQueueConfirmedE2E :: s -> RcvQueue -> C.PublicKeyX25519 -> C.DhSecretX25519 -> m () setSndQueueStatus :: s -> SndQueue -> QueueStatus -> m () -- Confirmations @@ -81,11 +80,13 @@ data RcvQueue = RcvQueue rcvPrivateKey :: RcvPrivateSignKey, -- | shared DH secret used to encrypt/decrypt message bodies from server to recipient rcvDhSecret :: RcvDhSecret, + -- | private DH key related to public sent to sender out-of-band (to agree simple per-queue e2e) + e2ePrivKey :: C.PrivateKeyX25519, + -- | public sender's DH key and agreed shared DH secret for simple per-queue e2e + e2eShared :: Maybe (C.PublicKeyX25519, C.DhSecretX25519), -- | sender queue ID sndId :: Maybe SMP.SenderId, - -- | TODO keys used for E2E encryption - these will change with double ratchet - decryptKey :: C.APrivateDecryptKey, - verifyKey :: Maybe C.APublicVerifyKey, + -- | queue status status :: QueueStatus } deriving (Eq, Show) @@ -93,10 +94,15 @@ data RcvQueue = RcvQueue -- | A send queue. SMP queue through which the agent sends messages to a recipient. data SndQueue = SndQueue { server :: SMPServer, + -- | sender queue ID sndId :: SMP.SenderId, + -- | key used by the sender to sign transmissions sndPrivateKey :: SndPrivateSignKey, - encryptKey :: C.APublicEncryptKey, - signKey :: C.APrivateSignKey, + -- | public DH key that was (or needs to be) sent to the recipient in SMP confirmation (to agree simple per-queue e2e) + e2ePubKey :: C.PublicKeyX25519, + -- | shared DH secret agreed for simple per-queue e2e encryption + e2eDhSecret :: C.DhSecretX25519, + -- | queue status status :: QueueStatus } deriving (Eq, Show) @@ -167,15 +173,13 @@ newtype ConnData = ConnData {connId :: ConnId} data NewConfirmation = NewConfirmation { connId :: ConnId, - senderKey :: SndPublicVerifyKey, - senderConnInfo :: ConnInfo + senderConf :: SMPConfirmation } data AcceptedConfirmation = AcceptedConfirmation { confirmationId :: ConfirmationId, connId :: ConnId, - senderKey :: SndPublicVerifyKey, - senderConnInfo :: ConnInfo, + senderConf :: SMPConfirmation, ownConnInfo :: ConnInfo } @@ -225,7 +229,7 @@ data SndMsgData = SndMsgData internalTs :: InternalTs, msgBody :: MsgBody, internalHash :: MsgHash, - previousMsgHash :: MsgHash + prevMsgHash :: MsgHash } data PendingMsg = PendingMsg @@ -234,10 +238,6 @@ data PendingMsg = PendingMsg } deriving (Show) --- * Broadcast types - -type BroadcastId = ByteString - -- * Message types -- | A message in either direction that is stored by the agent. diff --git a/src/Simplex/Messaging/Agent/Store/SQLite.hs b/src/Simplex/Messaging/Agent/Store/SQLite.hs index aab4111b6..b66537f60 100644 --- a/src/Simplex/Messaging/Agent/Store/SQLite.hs +++ b/src/Simplex/Messaging/Agent/Store/SQLite.hs @@ -250,19 +250,21 @@ instance (MonadUnliftIO m, MonadError StoreError m) => MonadAgentStore SQLiteSto |] [":status" := status, ":host" := host, ":port" := serializePort_ port, ":rcv_id" := rcvId] - setRcvQueueActive :: SQLiteStore -> RcvQueue -> C.APublicVerifyKey -> m () - setRcvQueueActive st RcvQueue {rcvId, server = SMPServer {host, port}} verifyKey = - -- ? throw error if queue does not exist? + setRcvQueueConfirmedE2E :: SQLiteStore -> RcvQueue -> C.PublicKeyX25519 -> C.DhSecretX25519 -> m () + setRcvQueueConfirmedE2E st RcvQueue {rcvId, server = SMPServer {host, port}} e2eSndPubKey e2eDhSecret = liftIO . withTransaction st $ \db -> DB.executeNamed db [sql| UPDATE rcv_queues - SET verify_key = :verify_key, status = :status - WHERE host = :host AND port = :port AND rcv_id = :rcv_id; + SET e2e_snd_pub_key = :e2e_snd_pub_key, + e2e_dh_secret = :e2e_dh_secret, + status = :status + WHERE host = :host AND port = :port AND rcv_id = :rcv_id |] - [ ":verify_key" := Just verifyKey, - ":status" := Active, + [ ":status" := Confirmed, + ":e2e_snd_pub_key" := e2eSndPubKey, + ":e2e_dh_secret" := e2eDhSecret, ":host" := host, ":port" := serializePort_ port, ":rcv_id" := rcvId @@ -282,16 +284,16 @@ instance (MonadUnliftIO m, MonadError StoreError m) => MonadAgentStore SQLiteSto [":status" := status, ":host" := host, ":port" := serializePort_ port, ":snd_id" := sndId] createConfirmation :: SQLiteStore -> TVar ChaChaDRG -> NewConfirmation -> m ConfirmationId - createConfirmation st gVar NewConfirmation {connId, senderKey, senderConnInfo} = + createConfirmation st gVar NewConfirmation {connId, senderConf = SMPConfirmation {senderKey, e2ePubKey, connInfo}} = liftIOEither . withTransaction st $ \db -> createWithRandomId gVar $ \confirmationId -> DB.execute db [sql| INSERT INTO conn_confirmations - (confirmation_id, conn_alias, sender_key, sender_conn_info, accepted) VALUES (?, ?, ?, ?, 0); + (confirmation_id, conn_alias, sender_key, e2e_snd_pub_key, sender_conn_info, accepted) VALUES (?, ?, ?, ?, ?, 0); |] - (confirmationId, connId, senderKey, senderConnInfo) + (confirmationId, connId, senderKey, e2ePubKey, connInfo) acceptConfirmation :: SQLiteStore -> ConfirmationId -> ConnInfo -> m AcceptedConfirmation acceptConfirmation st confirmationId ownConnInfo = @@ -311,14 +313,20 @@ instance (MonadUnliftIO m, MonadError StoreError m) => MonadAgentStore SQLiteSto <$> DB.query db [sql| - SELECT conn_alias, sender_key, sender_conn_info + SELECT conn_alias, sender_key, e2e_snd_pub_key, sender_conn_info FROM conn_confirmations WHERE confirmation_id = ?; |] (Only confirmationId) where - confirmation [(connId, senderKey, senderConnInfo)] = - Right $ AcceptedConfirmation {confirmationId, connId, senderKey, senderConnInfo, ownConnInfo} + confirmation [(connId, senderKey, e2ePubKey, connInfo)] = + Right + AcceptedConfirmation + { confirmationId, + connId, + senderConf = SMPConfirmation {senderKey, e2ePubKey, connInfo}, + ownConnInfo + } confirmation _ = Left SEConfirmationNotFound getAcceptedConfirmation :: SQLiteStore -> ConnId -> m AcceptedConfirmation @@ -328,14 +336,20 @@ instance (MonadUnliftIO m, MonadError StoreError m) => MonadAgentStore SQLiteSto <$> DB.query db [sql| - SELECT confirmation_id, sender_key, sender_conn_info, own_conn_info + SELECT confirmation_id, sender_key, e2e_snd_pub_key, sender_conn_info, own_conn_info FROM conn_confirmations WHERE conn_alias = ? AND accepted = 1; |] (Only connId) where - confirmation [(confirmationId, senderKey, senderConnInfo, ownConnInfo)] = - Right $ AcceptedConfirmation {confirmationId, connId, senderKey, senderConnInfo, ownConnInfo} + confirmation [(confirmationId, senderKey, e2ePubKey, connInfo, ownConnInfo)] = + Right + AcceptedConfirmation + { confirmationId, + connId, + senderConf = SMPConfirmation {senderKey, e2ePubKey, connInfo}, + ownConnInfo + } confirmation _ = Left SEConfirmationNotFound removeConfirmations :: SQLiteStore -> ConnId -> m () @@ -598,6 +612,7 @@ instance (ToField a, ToField b, ToField c, ToField d, ToField e, ToField f, [ toField a, toField b, toField c, toField d, toField e, toField f, toField g, toField h, toField i, toField j, toField k, toField l ] + {- ORMOLU_ENABLE -} -- * Server upsert helper @@ -621,13 +636,15 @@ upsertServer_ dbConn SMPServer {host, port, keyHash} = do insertRcvQueue_ :: DB.Connection -> ConnId -> RcvQueue -> IO () insertRcvQueue_ dbConn connId RcvQueue {..} = do let port_ = serializePort_ $ port server + e2eSndPubKey = fst <$> e2eShared :: Maybe C.PublicKeyX25519 + e2eDhSecret = snd <$> e2eShared :: Maybe C.DhSecretX25519 DB.executeNamed dbConn [sql| INSERT INTO rcv_queues - ( host, port, rcv_id, conn_alias, rcv_private_key, rcv_dh_secret, snd_id, decrypt_key, verify_key, status) + ( host, port, rcv_id, conn_alias, rcv_private_key, rcv_dh_secret, e2e_priv_key, e2e_snd_pub_key, e2e_dh_secret, snd_id, status) VALUES - (:host,:port,:rcv_id,:conn_alias,:rcv_private_key,:rcv_dh_secret,:snd_id,:decrypt_key,:verify_key,:status); + (:host,:port,:rcv_id,:conn_alias,:rcv_private_key,:rcv_dh_secret,:e2e_priv_key,:e2e_snd_pub_key,:e2e_dh_secret,:snd_id,:status); |] [ ":host" := host server, ":port" := port_, @@ -635,9 +652,10 @@ insertRcvQueue_ dbConn connId RcvQueue {..} = do ":conn_alias" := connId, ":rcv_private_key" := rcvPrivateKey, ":rcv_dh_secret" := rcvDhSecret, + ":e2e_priv_key" := e2ePrivKey, + ":e2e_snd_pub_key" := e2eSndPubKey, + ":e2e_dh_secret" := e2eDhSecret, ":snd_id" := sndId, - ":decrypt_key" := decryptKey, - ":verify_key" := verifyKey, ":status" := status ] @@ -670,17 +688,17 @@ insertSndQueue_ dbConn connId SndQueue {..} = do dbConn [sql| INSERT INTO snd_queues - ( host, port, snd_id, conn_alias, snd_private_key, encrypt_key, sign_key, status) + ( host, port, snd_id, conn_alias, snd_private_key, e2e_pub_key, e2e_dh_secret, status) VALUES - (:host,:port,:snd_id,:conn_alias,:snd_private_key,:encrypt_key,:sign_key,:status); + (:host,:port,:snd_id,:conn_alias,:snd_private_key,:e2e_pub_key,:e2e_dh_secret,:status); |] [ ":host" := host server, ":port" := port_, ":snd_id" := sndId, ":conn_alias" := connId, ":snd_private_key" := sndPrivateKey, - ":encrypt_key" := encryptKey, - ":sign_key" := signKey, + ":e2e_pub_key" := e2ePubKey, + ":e2e_dh_secret" := e2eDhSecret, ":status" := status ] @@ -732,26 +750,17 @@ getRcvQueueByConnAlias_ dbConn connId = dbConn [sql| SELECT s.key_hash, q.host, q.port, q.rcv_id, q.rcv_private_key, q.rcv_dh_secret, - q.snd_id, q.decrypt_key, q.verify_key, q.status + q.e2e_priv_key, q.e2e_snd_pub_key, q.e2e_dh_secret, q.snd_id, q.status FROM rcv_queues q INNER JOIN servers s ON q.host = s.host AND q.port = s.port WHERE q.conn_alias = ?; |] (Only connId) where - rcvQueue [(keyHash, host, port, rcvId, rcvPrivateKey, rcvDhSecret, sndId, decryptKey, verifyKey, status)] = + rcvQueue [(keyHash, host, port, rcvId, rcvPrivateKey, rcvDhSecret, e2ePrivKey, e2eSndPubKey, e2eDhSecret, sndId, status)] = let server = SMPServer host (deserializePort_ port) keyHash - in Just $ - RcvQueue - { server, - rcvId, - rcvPrivateKey, - rcvDhSecret, - sndId, - decryptKey, - verifyKey, - status - } + e2eShared = (,) <$> e2eSndPubKey <*> e2eDhSecret + in Just RcvQueue {server, rcvId, rcvPrivateKey, rcvDhSecret, e2ePrivKey, e2eShared, sndId, status} rcvQueue _ = Nothing getSndQueueByConnAlias_ :: DB.Connection -> ConnId -> IO (Maybe SndQueue) @@ -760,16 +769,17 @@ getSndQueueByConnAlias_ dbConn connId = <$> DB.query dbConn [sql| - SELECT s.key_hash, q.host, q.port, q.snd_id, q.snd_private_key, q.encrypt_key, q.sign_key, q.status + SELECT s.key_hash, q.host, q.port, q.snd_id, q.snd_private_key, + q.e2e_pub_key, q.e2e_dh_secret, q.status FROM snd_queues q INNER JOIN servers s ON q.host = s.host AND q.port = s.port WHERE q.conn_alias = ?; |] (Only connId) where - sndQueue [(keyHash, host, port, sndId, sndPrivateKey, encryptKey, signKey, status)] = + sndQueue [(keyHash, host, port, sndId, sndPrivateKey, e2ePubKey, e2eDhSecret, status)] = let server = SMPServer host (deserializePort_ port) keyHash - in Just $ SndQueue {server, sndId, sndPrivateKey, encryptKey, signKey, status} + in Just SndQueue {server, sndId, sndPrivateKey, e2ePubKey, e2eDhSecret, status} sndQueue _ = Nothing -- * upgradeRcvConnToDuplex helpers @@ -852,24 +862,23 @@ insertRcvMsgBase_ dbConn connId RcvMsgData {msgMeta, msgBody, internalRcvId} = d insertRcvMsgDetails_ :: DB.Connection -> ConnId -> RcvMsgData -> IO () insertRcvMsgDetails_ dbConn connId RcvMsgData {msgMeta, internalRcvId, internalHash, externalPrevSndHash} = do - let MsgMeta {integrity, recipient, sender, broker} = msgMeta + let MsgMeta {integrity, recipient, broker, sndMsgId} = msgMeta DB.executeNamed dbConn [sql| INSERT INTO rcv_messages - ( conn_alias, internal_rcv_id, internal_id, external_snd_id, external_snd_ts, + ( conn_alias, internal_rcv_id, internal_id, external_snd_id, broker_id, broker_ts, rcv_status, ack_brocker_ts, ack_sender_ts, internal_hash, external_prev_snd_hash, integrity) VALUES - (:conn_alias,:internal_rcv_id,:internal_id,:external_snd_id,:external_snd_ts, + (:conn_alias,:internal_rcv_id,:internal_id,:external_snd_id, :broker_id,:broker_ts,:rcv_status, NULL, NULL, :internal_hash,:external_prev_snd_hash,:integrity); |] [ ":conn_alias" := connId, ":internal_rcv_id" := internalRcvId, ":internal_id" := fst recipient, - ":external_snd_id" := fst sender, - ":external_snd_ts" := snd sender, + ":external_snd_id" := sndMsgId, ":broker_id" := fst broker, ":broker_ts" := snd broker, ":rcv_status" := Received, @@ -890,7 +899,7 @@ updateHashRcv_ dbConn connId RcvMsgData {msgMeta, internalHash, internalRcvId} = WHERE conn_alias = :conn_alias AND last_internal_rcv_msg_id = :last_internal_rcv_msg_id; |] - [ ":last_external_snd_msg_id" := fst (sender msgMeta), + [ ":last_external_snd_msg_id" := sndMsgId (msgMeta :: MsgMeta), ":last_rcv_msg_hash" := internalHash, ":conn_alias" := connId, ":last_internal_rcv_msg_id" := internalRcvId @@ -960,7 +969,7 @@ insertSndMsgDetails_ dbConn connId SndMsgData {..} = ":internal_id" := internalId, ":snd_status" := SndMsgCreated, ":internal_hash" := internalHash, - ":previous_msg_hash" := previousMsgHash + ":previous_msg_hash" := prevMsgHash ] updateHashSnd_ :: DB.Connection -> ConnId -> SndMsgData -> IO () diff --git a/src/Simplex/Messaging/Client.hs b/src/Simplex/Messaging/Client.hs index c5723907f..b2b3718e1 100644 --- a/src/Simplex/Messaging/Client.hs +++ b/src/Simplex/Messaging/Client.hs @@ -23,7 +23,7 @@ -- See https://github.com/simplex-chat/simplexmq/blob/master/protocol/simplex-messaging.md module Simplex.Messaging.Client ( -- * Connect (disconnect) client to (from) SMP server - SMPClient (blockSize), + SMPClient, getSMPClient, closeSMPClient, @@ -85,8 +85,7 @@ data SMPClient = SMPClient sentCommands :: TVar (Map CorrId Request), sndQ :: TBQueue SentRawTransmission, rcvQ :: TBQueue (SignedTransmission (Command 'Broker)), - msgQ :: TBQueue SMPServerTransmission, - blockSize :: Int + msgQ :: TBQueue SMPServerTransmission } -- | Type synonym for transmission from some SPM server queue. @@ -101,12 +100,7 @@ data SMPClientConfig = SMPClientConfig -- | timeout of TCP commands (microseconds) tcpTimeout :: Int, -- | period for SMP ping commands (microseconds) - smpPing :: Int, - -- | SMP transport block size - smpBlockSize :: Int, - -- | estimated maximum size of SMP command excluding message body, - -- determines the maximum allowed message size - smpCommandSize :: Int + smpPing :: Int } -- | Default SMP client configuration. @@ -116,9 +110,7 @@ smpDefaultConfig = { qSize = 16, defaultTransport = ("5223", transport @TLS), tcpTimeout = 4_000_000, - smpPing = 30_000_000, - smpBlockSize = 16 * 1024, -- TODO move to Protocol - smpCommandSize = 256 + smpPing = 30_000_000 } data Request = Request @@ -134,7 +126,7 @@ type Response = Either SMPClientError (Command 'Broker) -- A single queue can be used for multiple 'SMPClient' instances, -- as 'SMPServerTransmission' includes server information. getSMPClient :: SMPServer -> SMPClientConfig -> TBQueue SMPServerTransmission -> IO () -> IO (Either SMPClientError SMPClient) -getSMPClient smpServer cfg@SMPClientConfig {qSize, tcpTimeout, smpPing, smpBlockSize} msgQ disconnected = +getSMPClient smpServer cfg@SMPClientConfig {qSize, tcpTimeout, smpPing} msgQ disconnected = atomically mkSMPClient >>= runClient useTransport where mkSMPClient :: STM SMPClient @@ -148,7 +140,6 @@ getSMPClient smpServer cfg@SMPClientConfig {qSize, tcpTimeout, smpPing, smpBlock SMPClient { action = undefined, sessionId = undefined, - blockSize = undefined, connected, smpServer, tcpTimeout, @@ -168,8 +159,7 @@ getSMPClient smpServer cfg@SMPClientConfig {qSize, tcpTimeout, smpPing, smpBlock `finally` atomically (putTMVar thVar $ Left SMPNetworkError) th_ <- tcpTimeout `timeout` atomically (takeTMVar thVar) pure $ case th_ of - Just (Right THandle {sessionId, blockSize}) -> - Right c {action, sessionId, blockSize} + Just (Right THandle {sessionId}) -> Right c {action, sessionId} Just (Left e) -> Left e Nothing -> Left SMPNetworkError @@ -181,13 +171,13 @@ getSMPClient smpServer cfg@SMPClientConfig {qSize, tcpTimeout, smpPing, smpBlock client :: forall c. Transport c => TProxy c -> SMPClient -> TMVar (Either SMPClientError (THandle c)) -> c -> IO () client _ c thVar h = - runExceptT (clientHandshake h smpBlockSize) >>= \case + runExceptT (clientHandshake h) >>= \case Left e -> atomically . putTMVar thVar . Left $ SMPTransportError e - Right th@THandle {sessionId, blockSize} -> do + Right th@THandle {sessionId} -> do atomically $ do writeTVar (connected c) True putTMVar thVar $ Right th - let c' = c {sessionId, blockSize} :: SMPClient + let c' = c {sessionId} :: SMPClient raceAny_ [send c' th, process c', receive c' th, ping c'] `finally` disconnected diff --git a/src/Simplex/Messaging/Crypto.hs b/src/Simplex/Messaging/Crypto.hs index 163ae6069..639a477e9 100644 --- a/src/Simplex/Messaging/Crypto.hs +++ b/src/Simplex/Messaging/Crypto.hs @@ -5,7 +5,7 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE LambdaCase #-} -{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} @@ -32,39 +32,50 @@ module Simplex.Messaging.Crypto SAlgorithm (..), Alg (..), SignAlg (..), + DhAlg (..), DhAlgorithm, PrivateKey (..), PublicKey (..), + PrivateKeyX25519, + PublicKeyX25519, APrivateKey (..), APublicKey (..), APrivateSignKey (..), APublicVerifyKey (..), - APrivateDecryptKey (..), - APublicEncryptKey (..), - CryptoKey (..), + APrivateDhKey (..), + APublicDhKey (..), + CryptoPublicKey (..), CryptoPrivateKey (..), KeyPair, DhSecret (..), + DhSecretX25519, ADhSecret (..), CryptoDhSecret (..), KeyHash (..), generateKeyPair, generateKeyPair', generateSignatureKeyPair, - generateEncryptionKeyPair, + generateDhKeyPair, privateToX509, + -- * key encoding/decoding + serializePubKey, + serializePubKey', + serializePubKeyUri, + serializePubKeyUri', + strPubKeyP, + strPubKeyUriP, + encodeLenKey', + encodeLenKey, + binaryLenKeyP, + encodePubKey, + encodePubKey', + binaryPubKeyP, + encodePrivKey, + -- * E2E hybrid encryption scheme E2EEncryptionVersion, currentE2EVersion, - encrypt, - encrypt', - decrypt, - decrypt', - - -- * RSA OAEP encryption - encryptOAEP, - decryptOAEP, -- * sign/verify Signature (..), @@ -95,18 +106,24 @@ module Simplex.Messaging.Crypto bsToAuthTag, randomAesKey, randomIV, - aesKeyP, ivP, ivSize, -- * NaCl crypto_box + CbNonce (unCbNonce), cbEncrypt, cbDecrypt, cbNonce, + randomCbNonce, + cbNonceP, -- * SHA256 hash sha256Hash, + -- * Message padding / un-padding + pad, + unPad, + -- * Cryptography error type CryptoError (..), ) @@ -121,15 +138,10 @@ import qualified Crypto.Cipher.XSalsa as XSalsa import qualified Crypto.Error as CE import Crypto.Hash (Digest, SHA256 (..), hash) import qualified Crypto.MAC.Poly1305 as Poly1305 -import Crypto.Number.Generate (generateMax) -import Crypto.Number.Prime (findPrimeFrom) import qualified Crypto.PubKey.Curve25519 as X25519 import qualified Crypto.PubKey.Curve448 as X448 import qualified Crypto.PubKey.Ed25519 as Ed25519 import qualified Crypto.PubKey.Ed448 as Ed448 -import qualified Crypto.PubKey.RSA as R -import qualified Crypto.PubKey.RSA.OAEP as OAEP -import qualified Crypto.PubKey.RSA.PSS as PSS import Crypto.Random (getRandomBytes) import Data.ASN1.BinaryEncoding import Data.ASN1.Encoding @@ -154,9 +166,9 @@ import Data.X509 import Database.SQLite.Simple.FromField (FromField (..)) import Database.SQLite.Simple.ToField (ToField (..)) import GHC.TypeLits (ErrorMessage (..), TypeError) -import Network.Transport.Internal (decodeWord32, encodeWord32) -import Simplex.Messaging.Parsers (base64P, base64UriP, blobFieldParser, parseAll, parseE, parseString) -import Simplex.Messaging.Util (liftEitherError, (<$?>)) +import Network.Transport.Internal (decodeWord16, encodeWord16) +import Simplex.Messaging.Parsers (base64P, base64UriP, blobFieldParser, parseAll, parseString, word16P) +import Simplex.Messaging.Util ((<$?>)) type E2EEncryptionVersion = Word16 @@ -164,11 +176,10 @@ currentE2EVersion :: E2EEncryptionVersion currentE2EVersion = 1 -- | Cryptographic algorithms. -data Algorithm = RSA | Ed25519 | Ed448 | X25519 | X448 +data Algorithm = Ed25519 | Ed448 | X25519 | X448 -- | Singleton types for 'Algorithm'. data SAlgorithm :: Algorithm -> Type where - SRSA :: SAlgorithm RSA SEd25519 :: SAlgorithm Ed25519 SEd448 :: SAlgorithm Ed448 SX25519 :: SAlgorithm X25519 @@ -185,9 +196,12 @@ data SignAlg (AlgorithmI a, SignatureAlgorithm a) => SignAlg (SAlgorithm a) -class AlgorithmI (a :: Algorithm) where sAlgorithm :: SAlgorithm a +data DhAlg + = forall a. + (AlgorithmI a, DhAlgorithm a) => + DhAlg (SAlgorithm a) -instance AlgorithmI RSA where sAlgorithm = SRSA +class AlgorithmI (a :: Algorithm) where sAlgorithm :: SAlgorithm a instance AlgorithmI Ed25519 where sAlgorithm = SEd25519 @@ -198,7 +212,6 @@ instance AlgorithmI X25519 where sAlgorithm = SX25519 instance AlgorithmI X448 where sAlgorithm = SX448 instance TestEquality SAlgorithm where - testEquality SRSA SRSA = Just Refl testEquality SEd25519 SEd25519 = Just Refl testEquality SEd448 SEd448 = Just Refl testEquality SX25519 SX25519 = Just Refl @@ -207,7 +220,6 @@ instance TestEquality SAlgorithm where -- | GADT for public keys. data PublicKey (a :: Algorithm) where - PublicKeyRSA :: R.PublicKey -> PublicKey RSA PublicKeyEd25519 :: Ed25519.PublicKey -> PublicKey Ed25519 PublicKeyEd448 :: Ed448.PublicKey -> PublicKey Ed448 PublicKeyX25519 :: X25519.PublicKey -> PublicKey X25519 @@ -229,9 +241,10 @@ instance Eq APublicKey where deriving instance Show APublicKey +type PublicKeyX25519 = PublicKey X25519 + -- | GADT for private keys. data PrivateKey (a :: Algorithm) where - PrivateKeyRSA :: {privateKeyRSA :: R.PrivateKey} -> PrivateKey RSA PrivateKeyEd25519 :: Ed25519.SecretKey -> Ed25519.PublicKey -> PrivateKey Ed25519 PrivateKeyEd448 :: Ed448.SecretKey -> Ed448.PublicKey -> PrivateKey Ed448 PrivateKeyX25519 :: X25519.SecretKey -> PrivateKey X25519 @@ -253,12 +266,13 @@ instance Eq APrivateKey where deriving instance Show APrivateKey +type PrivateKeyX25519 = PrivateKey X25519 + class AlgorithmPrefix k where algorithmPrefix :: k -> ByteString instance AlgorithmPrefix (SAlgorithm a) where algorithmPrefix = \case - SRSA -> "rsa" SEd25519 -> "ed25519" SEd448 -> "ed448" SX25519 -> "x25519" @@ -278,7 +292,6 @@ instance AlgorithmPrefix APrivateKey where prefixAlgorithm :: ByteString -> Either String Alg prefixAlgorithm = \case - "rsa" -> Right $ Alg SRSA "ed25519" -> Right $ Alg SEd25519 "ed448" -> Right $ Alg SEd448 "x25519" -> Right $ Alg SX25519 @@ -289,7 +302,6 @@ algP :: Parser Alg algP = prefixAlgorithm <$?> A.takeTill (== ':') type family SignatureAlgorithm (a :: Algorithm) :: Constraint where - SignatureAlgorithm RSA = () SignatureAlgorithm Ed25519 = () SignatureAlgorithm Ed448 = () SignatureAlgorithm a = @@ -297,7 +309,6 @@ type family SignatureAlgorithm (a :: Algorithm) :: Constraint where signatureAlgorithm :: SAlgorithm a -> Maybe (Dict (SignatureAlgorithm a)) signatureAlgorithm = \case - SRSA -> Just Dict SEd25519 -> Just Dict SEd448 -> Just Dict _ -> Nothing @@ -326,39 +337,29 @@ instance Eq APublicVerifyKey where deriving instance Show APublicVerifyKey -type family EncryptionAlgorithm (a :: Algorithm) :: Constraint where - EncryptionAlgorithm RSA = () - EncryptionAlgorithm a = - (Int ~ Bool, TypeError (Text "Algorithm " :<>: ShowType a :<>: Text " cannot be used to encrypt/decrypt")) - -encryptionAlgorithm :: SAlgorithm a -> Maybe (Dict (EncryptionAlgorithm a)) -encryptionAlgorithm = \case - SRSA -> Just Dict - _ -> Nothing - -data APrivateDecryptKey +data APrivateDhKey = forall a. - (AlgorithmI a, EncryptionAlgorithm a) => - APrivateDecryptKey (SAlgorithm a) (PrivateKey a) + (AlgorithmI a, DhAlgorithm a) => + APrivateDhKey (SAlgorithm a) (PrivateKey a) -instance Eq APrivateDecryptKey where - APrivateDecryptKey a k == APrivateDecryptKey a' k' = case testEquality a a' of +instance Eq APrivateDhKey where + APrivateDhKey a k == APrivateDhKey a' k' = case testEquality a a' of Just Refl -> k == k' Nothing -> False -deriving instance Show APrivateDecryptKey +deriving instance Show APrivateDhKey -data APublicEncryptKey +data APublicDhKey = forall a. - (AlgorithmI a, EncryptionAlgorithm a) => - APublicEncryptKey (SAlgorithm a) (PublicKey a) + (AlgorithmI a, DhAlgorithm a) => + APublicDhKey (SAlgorithm a) (PublicKey a) -instance Eq APublicEncryptKey where - APublicEncryptKey a k == APublicEncryptKey a' k' = case testEquality a a' of +instance Eq APublicDhKey where + APublicDhKey a k == APublicDhKey a' k' = case testEquality a a' of Just Refl -> k == k' Nothing -> False -deriving instance Show APublicEncryptKey +deriving instance Show APublicDhKey data DhSecret (a :: Algorithm) where DhSecretX25519 :: X25519.DhSecret -> DhSecret X25519 @@ -373,6 +374,8 @@ data ADhSecret (AlgorithmI a, DhAlgorithm a) => ADhSecret (SAlgorithm a) (DhSecret a) +type DhSecretX25519 = DhSecret X25519 + type family DhAlgorithm (a :: Algorithm) :: Constraint where DhAlgorithm X25519 = () DhAlgorithm X448 = () @@ -425,39 +428,67 @@ dhSecret' (ADhSecret a s) = case testEquality a $ sAlgorithm @a of _ -> Left "bad DH secret algorithm" -- | Class for all key types -class CryptoKey k where - keySize :: k -> Int - - validKeySize :: k -> Bool - - -- | base64 X509 key encoding with algorithm prefix - serializeKey :: k -> ByteString - - -- | base64url X509 key encoding with algorithm prefix - serializeKeyUri :: k -> ByteString - - -- | binary X509 key encoding - encodeKey :: k -> ByteString - - -- | base64 X509 (with algorithm prefix) key parser - strKeyP :: Parser k - - -- | base64url X509 (with algorithm prefix) key parser - strKeyUriP :: Parser k - - -- | binary X509 key parser - binaryKeyP :: Parser k +class CryptoPublicKey k where + aPubKey :: k -> APublicKey + pubKey :: APublicKey -> Either String k -- | X509 encoding of any public key. -instance CryptoKey APublicKey where - keySize (APublicKey _ k) = keySize k - validKeySize (APublicKey _ k) = validKeySize k - serializeKey (APublicKey _ k) = serializeKey k - serializeKeyUri (APublicKey _ k) = serializeKeyUri k - encodeKey (APublicKey _ k) = encodeKey k - strKeyP = strPublicKeyP_ base64P - strKeyUriP = strPublicKeyP_ base64UriP - binaryKeyP = decodePubKey <$?> A.takeByteString +instance CryptoPublicKey APublicKey where + aPubKey = id + pubKey = Right + +-- | X509 encoding of signature public key. +instance CryptoPublicKey APublicVerifyKey where + aPubKey (APublicVerifyKey a k) = APublicKey a k + pubKey (APublicKey a k) = case signatureAlgorithm a of + Just Dict -> Right $ APublicVerifyKey a k + _ -> Left "key does not support signature algorithms" + +-- | X509 encoding of DH public key. +instance CryptoPublicKey APublicDhKey where + aPubKey (APublicDhKey a k) = APublicKey a k + pubKey (APublicKey a k) = case dhAlgorithm a of + Just Dict -> Right $ APublicDhKey a k + _ -> Left "key does not support DH algorithms" + +-- | X509 encoding of 'PublicKey'. +instance forall a. AlgorithmI a => CryptoPublicKey (PublicKey a) where + aPubKey k = APublicKey (sAlgorithm @a) k + pubKey (APublicKey a k) = case testEquality a $ sAlgorithm @a of + Just Refl -> Right k + _ -> Left "bad key algorithm" + +-- | base64 X509 key encoding with algorithm prefix +serializePubKey :: CryptoPublicKey k => k -> ByteString +serializePubKey k = case aPubKey k of APublicKey _ k' -> serializePubKey' k' +{-# INLINE serializePubKey #-} + +-- | base64url X509 key encoding with algorithm prefix +serializePubKeyUri :: CryptoPublicKey k => k -> ByteString +serializePubKeyUri k = case aPubKey k of APublicKey _ k' -> serializePubKeyUri' k' +{-# INLINE serializePubKeyUri #-} + +serializePubKey' :: AlgorithmI a => PublicKey a -> ByteString +serializePubKey' k = algorithmPrefix k <> ":" <> encode (encodePubKey' k) + +serializePubKeyUri' :: AlgorithmI a => PublicKey a -> ByteString +serializePubKeyUri' k = algorithmPrefix k <> ":" <> U.encode (encodePubKey' k) + +-- | base64 X509 (with algorithm prefix) key parser +strPubKeyP :: CryptoPublicKey k => Parser k +strPubKeyP = pubKey <$?> aStrPubKeyP +{-# INLINE strPubKeyP #-} + +-- | base64url X509 (with algorithm prefix) key parser +strPubKeyUriP :: CryptoPublicKey k => Parser k +strPubKeyUriP = pubKey <$?> aStrPubKeyUriP +{-# INLINE strPubKeyUriP #-} + +aStrPubKeyP :: Parser APublicKey +aStrPubKeyP = strPublicKeyP_ base64P + +aStrPubKeyUriP :: Parser APublicKey +aStrPubKeyUriP = strPublicKeyP_ base64UriP strPublicKeyP_ :: Parser ByteString -> Parser APublicKey strPublicKeyP_ b64P = do @@ -467,135 +498,81 @@ strPublicKeyP_ b64P = do Just Refl -> pure k _ -> fail $ "public key algorithm " <> show a <> " does not match prefix" --- | X509 encoding of signature public key. -instance CryptoKey APublicVerifyKey where - keySize (APublicVerifyKey _ k) = keySize k - validKeySize (APublicVerifyKey _ k) = validKeySize k - serializeKey (APublicVerifyKey _ k) = serializeKey k - serializeKeyUri (APublicVerifyKey _ k) = serializeKeyUri k - encodeKey (APublicVerifyKey _ k) = encodeKey k - strKeyP = pubVerifyKey <$?> strKeyP - strKeyUriP = pubVerifyKey <$?> strKeyUriP - binaryKeyP = pubVerifyKey <$?> binaryKeyP +encodeLenKey :: CryptoPublicKey k => k -> ByteString +encodeLenKey k = case aPubKey k of APublicKey _ k' -> encodeLenKey' k' +{-# INLINE encodeLenKey #-} --- | X509 encoding of encryption public key. -instance CryptoKey APublicEncryptKey where - keySize (APublicEncryptKey _ k) = keySize k - validKeySize (APublicEncryptKey _ k) = validKeySize k - serializeKey (APublicEncryptKey _ k) = serializeKey k - serializeKeyUri (APublicEncryptKey _ k) = serializeKeyUri k - encodeKey (APublicEncryptKey _ k) = encodeKey k - strKeyP = pubEncryptKey <$?> strKeyP - strKeyUriP = pubEncryptKey <$?> strKeyUriP - binaryKeyP = pubEncryptKey <$?> binaryKeyP +-- | binary X509 key encoding with 2-bytes length prefix +encodeLenKey' :: PublicKey a -> ByteString +encodeLenKey' k = + let s = encodePubKey' k + len = fromIntegral $ B.length s + in encodeWord16 len <> s +{-# INLINE encodeLenKey' #-} --- | X509 encoding of 'PublicKey'. -instance forall a. AlgorithmI a => CryptoKey (PublicKey a) where - keySize = \case - PublicKeyRSA k -> R.public_size k - PublicKeyEd25519 _ -> Ed25519.publicKeySize - PublicKeyEd448 _ -> Ed448.publicKeySize - PublicKeyX25519 _ -> x25519_size - PublicKeyX448 _ -> x448_size - validKeySize = \case - PublicKeyRSA k -> validRSAKeySize $ R.public_size k - _ -> True - serializeKey k = algorithmPrefix k <> ":" <> encode (encodeKey k) - serializeKeyUri k = algorithmPrefix k <> ":" <> U.encode (encodeKey k) - encodeKey = encodeASNObj . publicToX509 - strKeyP = pubKey' <$?> strKeyP - strKeyUriP = pubKey' <$?> strKeyUriP - binaryKeyP = pubKey' <$?> binaryKeyP +-- | binary X509 key parser with 2-bytes length prefix +binaryLenKeyP :: CryptoPublicKey k => Parser k +binaryLenKeyP = do + len <- fromIntegral <$> word16P + parseAll binaryPubKeyP <$?> A.take len --- | X509 encoding of any private key. -instance CryptoKey APrivateKey where - keySize (APrivateKey _ k) = keySize k - validKeySize (APrivateKey _ k) = validKeySize k - serializeKey (APrivateKey _ k) = serializeKey k - serializeKeyUri (APrivateKey _ k) = serializeKeyUri k - encodeKey (APrivateKey _ k) = encodeKey k - strKeyP = strPrivateKeyP_ base64P - strKeyUriP = strPrivateKeyP_ base64UriP - binaryKeyP = decodePrivKey <$?> A.takeByteString +encodePubKey :: CryptoPublicKey pk => pk -> ByteString +encodePubKey k = case aPubKey k of APublicKey _ k' -> encodePubKey' k' +{-# INLINE encodePubKey #-} -strPrivateKeyP_ :: Parser ByteString -> Parser APrivateKey -strPrivateKeyP_ b64P = do - Alg a <- algP <* A.char ':' - k@(APrivateKey a' _) <- decodePrivKey <$?> b64P - case testEquality a a' of - Just Refl -> pure k - _ -> fail $ "private key algorithm " <> show a <> " does not match prefix" +encodePubKey' :: PublicKey a -> ByteString +encodePubKey' = encodeASNObj . publicToX509 --- | X509 encoding of signature private key. -instance CryptoKey APrivateSignKey where - keySize (APrivateSignKey _ k) = keySize k - validKeySize (APrivateSignKey _ k) = validKeySize k - serializeKey (APrivateSignKey _ k) = serializeKey k - serializeKeyUri (APrivateSignKey _ k) = serializeKeyUri k - encodeKey (APrivateSignKey _ k) = encodeKey k - strKeyP = privSignKey <$?> strKeyP - strKeyUriP = privSignKey <$?> strKeyUriP - binaryKeyP = privSignKey <$?> binaryKeyP +binaryPubKeyP :: CryptoPublicKey pk => Parser pk +binaryPubKeyP = pubKey <$?> aBinaryPubKeyP +{-# INLINE binaryPubKeyP #-} --- | X509 encoding of encryption private key. -instance CryptoKey APrivateDecryptKey where - keySize (APrivateDecryptKey _ k) = keySize k - validKeySize (APrivateDecryptKey _ k) = validKeySize k - serializeKey (APrivateDecryptKey _ k) = serializeKey k - serializeKeyUri (APrivateDecryptKey _ k) = serializeKeyUri k - encodeKey (APrivateDecryptKey _ k) = encodeKey k - strKeyP = privDecryptKey <$?> strKeyP - strKeyUriP = privDecryptKey <$?> strKeyUriP - binaryKeyP = privDecryptKey <$?> binaryKeyP +aBinaryPubKeyP :: Parser APublicKey +aBinaryPubKeyP = decodePubKey <$?> A.takeByteString --- | X509 encoding of 'PrivateKey'. -instance AlgorithmI a => CryptoKey (PrivateKey a) where - keySize = \case - PrivateKeyRSA k -> rsaPrivateKeySize k - PrivateKeyEd25519 _ _ -> Ed25519.secretKeySize - PrivateKeyEd448 _ _ -> Ed448.secretKeySize - PrivateKeyX25519 _ -> x25519_size - PrivateKeyX448 _ -> x448_size - validKeySize = \case - PrivateKeyRSA k -> validRSAKeySize $ rsaPrivateKeySize k - _ -> True - serializeKey k = algorithmPrefix k <> ":" <> encode (encodeKey k) - serializeKeyUri k = algorithmPrefix k <> ":" <> U.encode (encodeKey k) - encodeKey = encodeASNObj . privateToX509 - strKeyP = privKey' <$?> strKeyP - strKeyUriP = privKey' <$?> strKeyUriP - binaryKeyP = privKey' <$?> binaryKeyP - -type family PublicKeyType pk where - PublicKeyType APrivateKey = APublicKey - PublicKeyType APrivateSignKey = APublicVerifyKey - PublicKeyType APrivateDecryptKey = APublicEncryptKey - PublicKeyType (PrivateKey a) = PublicKey a - -class CryptoPrivateKey pk where publicKey :: pk -> PublicKeyType pk +class CryptoPrivateKey pk where + aPrivKey :: pk -> APrivateKey + privKey :: APrivateKey -> Either String pk instance CryptoPrivateKey APrivateKey where - publicKey (APrivateKey a k) = APublicKey a $ publicKey k + aPrivKey = id + privKey = Right instance CryptoPrivateKey APrivateSignKey where - publicKey (APrivateSignKey a k) = APublicVerifyKey a $ publicKey k + aPrivKey (APrivateSignKey a k) = APrivateKey a k + privKey (APrivateKey a k) = case signatureAlgorithm a of + Just Dict -> Right $ APrivateSignKey a k + _ -> Left "key does not support signature algorithms" -instance CryptoPrivateKey APrivateDecryptKey where - publicKey (APrivateDecryptKey a k) = APublicEncryptKey a $ publicKey k +instance CryptoPrivateKey APrivateDhKey where + aPrivKey (APrivateDhKey a k) = APrivateKey a k + privKey (APrivateKey a k) = case dhAlgorithm a of + Just Dict -> Right $ APrivateDhKey a k + _ -> Left "key does not support DH algorithm" -instance CryptoPrivateKey (PrivateKey a) where - publicKey = \case - PrivateKeyRSA k -> PublicKeyRSA $ R.private_pub k - PrivateKeyEd25519 _ k -> PublicKeyEd25519 k - PrivateKeyEd448 _ k -> PublicKeyEd448 k - PrivateKeyX25519 k -> PublicKeyX25519 $ X25519.toPublic k - PrivateKeyX448 k -> PublicKeyX448 $ X448.toPublic k +instance AlgorithmI a => CryptoPrivateKey (PrivateKey a) where + aPrivKey k = APrivateKey (sAlgorithm @a) k + privKey (APrivateKey a k) = case testEquality a $ sAlgorithm @a of + Just Refl -> Right k + _ -> Left "bad key algorithm" + +encodePrivKey :: CryptoPrivateKey pk => pk -> ByteString +encodePrivKey k = case aPrivKey k of APrivateKey _ k' -> encodePrivKey' k' + +encodePrivKey' :: PrivateKey a -> ByteString +encodePrivKey' = encodeASNObj . privateToX509 + +binaryPrivKeyP :: CryptoPrivateKey pk => Parser pk +binaryPrivKeyP = privKey <$?> aBinaryPrivKeyP + +aBinaryPrivKeyP :: Parser APrivateKey +aBinaryPrivKeyP = decodePrivKey <$?> A.takeByteString instance AlgorithmI a => IsString (PrivateKey a) where - fromString = parseString $ decode >=> decodePrivKey >=> privKey' + fromString = parseString $ decode >=> decodePrivKey >=> privKey instance AlgorithmI a => IsString (PublicKey a) where - fromString = parseString $ decode >=> decodePubKey >=> pubKey' + fromString = parseString $ decode >=> decodePubKey >=> pubKey -- | Tuple of RSA 'PublicKey' and 'PrivateKey'. type KeyPair a = (PublicKey a, PrivateKey a) @@ -604,24 +581,19 @@ type AKeyPair = (APublicKey, APrivateKey) type ASignatureKeyPair = (APublicVerifyKey, APrivateSignKey) -type AnEncryptionKeyPair = (APublicEncryptKey, APrivateDecryptKey) +type ADhKeyPair = (APublicDhKey, APrivateDhKey) -generateKeyPair :: AlgorithmI a => Int -> SAlgorithm a -> IO AKeyPair -generateKeyPair size a = bimap (APublicKey a) (APrivateKey a) <$> generateKeyPair' size +generateKeyPair :: AlgorithmI a => SAlgorithm a -> IO AKeyPair +generateKeyPair a = bimap (APublicKey a) (APrivateKey a) <$> generateKeyPair' -generateSignatureKeyPair :: - (AlgorithmI a, SignatureAlgorithm a) => Int -> SAlgorithm a -> IO ASignatureKeyPair -generateSignatureKeyPair size a = - bimap (APublicVerifyKey a) (APrivateSignKey a) <$> generateKeyPair' size +generateSignatureKeyPair :: (AlgorithmI a, SignatureAlgorithm a) => SAlgorithm a -> IO ASignatureKeyPair +generateSignatureKeyPair a = bimap (APublicVerifyKey a) (APrivateSignKey a) <$> generateKeyPair' -generateEncryptionKeyPair :: - (AlgorithmI a, EncryptionAlgorithm a) => Int -> SAlgorithm a -> IO AnEncryptionKeyPair -generateEncryptionKeyPair size a = - bimap (APublicEncryptKey a) (APrivateDecryptKey a) <$> generateKeyPair' size +generateDhKeyPair :: (AlgorithmI a, DhAlgorithm a) => SAlgorithm a -> IO ADhKeyPair +generateDhKeyPair a = bimap (APublicDhKey a) (APrivateDhKey a) <$> generateKeyPair' -generateKeyPair' :: forall a. AlgorithmI a => Int -> IO (KeyPair a) -generateKeyPair' size = case sAlgorithm @a of - SRSA -> generateKeyPairRSA size +generateKeyPair' :: forall a. AlgorithmI a => IO (KeyPair a) +generateKeyPair' = case sAlgorithm @a of SEd25519 -> Ed25519.generateSecretKey >>= \pk -> let k = Ed25519.toPublic pk @@ -639,23 +611,31 @@ generateKeyPair' size = case sAlgorithm @a of let k = X448.toPublic pk in pure (PublicKeyX448 k, PrivateKeyX448 pk) -instance ToField APrivateSignKey where toField = toField . encodeKey +instance ToField APrivateSignKey where toField = toField . encodePrivKey -instance ToField APublicVerifyKey where toField = toField . encodeKey +instance ToField APublicVerifyKey where toField = toField . encodePubKey -instance ToField APrivateDecryptKey where toField = toField . encodeKey +instance ToField APrivateDhKey where toField = toField . encodePrivKey -instance ToField APublicEncryptKey where toField = toField . encodeKey +instance ToField APublicDhKey where toField = toField . encodePubKey -instance (Typeable a, AlgorithmI a) => ToField (DhSecret a) where toField = toField . dhSecretBytes +instance ToField (PrivateKey a) where toField = toField . encodePrivKey' -instance FromField APrivateSignKey where fromField = blobFieldParser binaryKeyP +instance ToField (PublicKey a) where toField = toField . encodePubKey' -instance FromField APublicVerifyKey where fromField = blobFieldParser binaryKeyP +instance AlgorithmI a => ToField (DhSecret a) where toField = toField . dhSecretBytes -instance FromField APrivateDecryptKey where fromField = blobFieldParser binaryKeyP +instance FromField APrivateSignKey where fromField = blobFieldParser binaryPrivKeyP -instance FromField APublicEncryptKey where fromField = blobFieldParser binaryKeyP +instance FromField APublicVerifyKey where fromField = blobFieldParser binaryPubKeyP + +instance FromField APrivateDhKey where fromField = blobFieldParser binaryPrivKeyP + +instance FromField APublicDhKey where fromField = blobFieldParser binaryPubKeyP + +instance (Typeable a, AlgorithmI a) => FromField (PrivateKey a) where fromField = blobFieldParser binaryPrivKeyP + +instance (Typeable a, AlgorithmI a) => FromField (PublicKey a) where fromField = blobFieldParser binaryPubKeyP instance (Typeable a, AlgorithmI a) => FromField (DhSecret a) where fromField = blobFieldParser dhSecretP @@ -663,7 +643,6 @@ instance IsString (Maybe ASignature) where fromString = parseString $ decode >=> decodeSignature data Signature (a :: Algorithm) where - SignatureRSA :: ByteString -> Signature RSA SignatureEd25519 :: Ed25519.Signature -> Signature Ed25519 SignatureEd448 :: Ed448.Signature -> Signature Ed448 @@ -692,16 +671,13 @@ class CryptoSignature s where instance CryptoSignature ASignature where signatureBytes (ASignature _ sig) = signatureBytes sig decodeSignature s - | l == Ed25519.signatureSize = + | B.length s == Ed25519.signatureSize = ASignature SEd25519 . SignatureEd25519 <$> ed Ed25519.signature s - | l == Ed448.signatureSize = + | B.length s == Ed448.signatureSize = ASignature SEd448 . SignatureEd448 <$> ed Ed448.signature s - | l == 128 || l == 256 || l == 384 || l == 512 = rsa s | otherwise = Left "bad signature size" where - l = B.length s ed alg = first show . CE.eitherCryptoError . alg - rsa = Right . ASignature SRSA . SignatureRSA instance CryptoSignature (Maybe ASignature) where signatureBytes = maybe "" signatureBytes @@ -711,7 +687,6 @@ instance CryptoSignature (Maybe ASignature) where instance AlgorithmI a => CryptoSignature (Signature a) where signatureBytes = \case - SignatureRSA s -> s SignatureEd25519 s -> BA.convert s SignatureEd448 s -> BA.convert s decodeSignature s = do @@ -724,7 +699,6 @@ class SignatureSize s where signatureSize :: s -> Int instance SignatureSize (Signature a) where signatureSize = \case - SignatureRSA s -> B.length s SignatureEd25519 _ -> Ed25519.signatureSize SignatureEd448 _ -> Ed448.signatureSize @@ -736,30 +710,17 @@ instance SignatureSize APublicVerifyKey where instance SignatureAlgorithm a => SignatureSize (PrivateKey a) where signatureSize = \case - PrivateKeyRSA k -> rsaPrivateKeySize k PrivateKeyEd25519 _ _ -> Ed25519.signatureSize PrivateKeyEd448 _ _ -> Ed448.signatureSize instance SignatureAlgorithm a => SignatureSize (PublicKey a) where signatureSize = \case - PublicKeyRSA k -> R.public_size k PublicKeyEd25519 _ -> Ed25519.signatureSize PublicKeyEd448 _ -> Ed448.signatureSize -rsaPrivateKeySize :: R.PrivateKey -> Int -rsaPrivateKeySize = R.public_size . R.private_pub - -- | Various cryptographic or related errors. data CryptoError - = -- | RSA OAEP encryption error - RSAEncryptError R.Error - | -- | RSA OAEP decryption error - RSADecryptError R.Error - | -- | RSA PSS signature error - RSASignError R.Error - | -- | Unsupported signing algorithm - UnsupportedAlgorithm - | -- | AES initialization error + = -- | AES initialization error AESCipherError CE.CryptoError | -- | IV generation error CryptoIVError @@ -767,7 +728,8 @@ data CryptoError AESDecryptError | -- CryptoBox decryption error CBDecryptError - | -- | message does not fit in SMP block + | -- | message is larger that allowed padded length minus 2 (to prepend message length) + -- (or required un-padded length is larger than the message length) CryptoLargeMsgError | -- | failure parsing message header CryptoHeaderError String @@ -781,47 +743,21 @@ data CryptoError CERatchetDuplicateMessage deriving (Eq, Show, Exception) -pubExpRange :: Integer -pubExpRange = 2 ^ (1024 :: Int) - aesKeySize :: Int aesKeySize = 256 `div` 8 authTagSize :: Int authTagSize = 128 `div` 8 --- | Generate RSA key pair. -generateKeyPairRSA :: Int -> IO (KeyPair RSA) -generateKeyPairRSA size = loop - where - publicExponent = findPrimeFrom . (+ 3) <$> generateMax pubExpRange - loop = do - (k, pk) <- R.generate size =<< publicExponent - let n = R.public_n k - d = R.private_d pk - if d * d < n - then loop - else pure (PublicKeyRSA k, PrivateKeyRSA pk) - x25519_size :: Int x25519_size = 32 x448_size :: Int x448_size = 448 `quot` 8 -validRSAKeySize :: Int -> Bool -validRSAKeySize n = n == 128 || n == 256 || n == 384 || n == 512 - validSignatureSize :: Int -> Bool validSignatureSize n = - n == Ed25519.signatureSize || n == Ed448.signatureSize || validRSAKeySize n - -data Header = Header - { aesKey :: Key, - ivBytes :: IV, - authTag :: AES.AuthTag, - msgSize :: Int - } + n == Ed25519.signatureSize || n == Ed448.signatureSize -- | AES key newtype. newtype Key = Key {unKey :: ByteString} @@ -846,62 +782,10 @@ instance FromField KeyHash where fromField = blobFieldParser $ KeyHash <$> base6 sha256Hash :: ByteString -> ByteString sha256Hash = BA.convert . (hash :: ByteString -> Digest SHA256) -serializeHeader :: Header -> ByteString -serializeHeader Header {aesKey, ivBytes, authTag, msgSize} = - unKey aesKey <> unIV ivBytes <> authTagToBS authTag <> (encodeWord32 . fromIntegral) msgSize - -headerP :: Parser Header -headerP = do - aesKey <- aesKeyP - ivBytes <- ivP - authTag <- bsToAuthTag <$> A.take authTagSize - msgSize <- fromIntegral . decodeWord32 <$> A.take 4 - return Header {aesKey, ivBytes, authTag, msgSize} - --- | AES256 key parser. -aesKeyP :: Parser Key -aesKeyP = Key <$> A.take aesKeySize - -- | IV bytes parser. ivP :: Parser IV ivP = IV <$> A.take (ivSize @AES256) -parseHeader :: ByteString -> ExceptT CryptoError IO Header -parseHeader = parseE CryptoHeaderError headerP - --- * E2E hybrid encryption scheme - --- | Legacy hybrid E2E encryption of SMP agent messages (RSA-OAEP/AES-256-GCM-SHA256). --- --- https://github.com/simplex-chat/simplexmq/blob/master/rfcs/2021-01-26-crypto.md#e2e-encryption -encrypt' :: PublicKey a -> Int -> ByteString -> ExceptT CryptoError IO ByteString -encrypt' k@(PublicKeyRSA _) paddedSize msg = do - aesKey <- liftIO randomAesKey - ivBytes <- liftIO randomIV - (authTag, msg') <- encryptAES aesKey ivBytes paddedSize msg - let header = Header {aesKey, ivBytes, authTag, msgSize = B.length msg} - encHeader <- encryptOAEP k $ serializeHeader header - return $ encHeader <> msg' -encrypt' _ _ _ = throwE UnsupportedAlgorithm - --- | Legacy hybrid E2E decryption of SMP agent messages (RSA-OAEP/AES-256-GCM-SHA256). --- --- https://github.com/simplex-chat/simplexmq/blob/master/rfcs/2021-01-26-crypto.md#e2e-encryption -decrypt' :: PrivateKey a -> ByteString -> ExceptT CryptoError IO ByteString -decrypt' pk@(PrivateKeyRSA _) msg'' = do - let (encHeader, msg') = B.splitAt (keySize pk) msg'' - header <- decryptOAEP pk encHeader - Header {aesKey, ivBytes, authTag, msgSize} <- parseHeader header - msg <- decryptAES aesKey ivBytes msg' authTag - return $ B.take msgSize msg -decrypt' _ _ = throwE UnsupportedAlgorithm - -encrypt :: APublicEncryptKey -> Int -> ByteString -> ExceptT CryptoError IO ByteString -encrypt (APublicEncryptKey _ k) = encrypt' k - -decrypt :: APrivateDecryptKey -> ByteString -> ExceptT CryptoError IO ByteString -decrypt (APrivateDecryptKey _ pk) = decrypt' pk - -- | AEAD-GCM encryption with empty associated data. -- -- Used as part of hybrid E2E encryption scheme and for SMP transport blocks encryption. @@ -912,15 +796,10 @@ encryptAES key iv paddedLen = encryptAEAD key iv paddedLen "" -- -- Used as part of hybrid E2E encryption scheme and for SMP transport blocks encryption. encryptAEAD :: Key -> IV -> Int -> ByteString -> ByteString -> ExceptT CryptoError IO (AES.AuthTag, ByteString) -encryptAEAD aesKey ivBytes paddedSize ad msg = do +encryptAEAD aesKey ivBytes paddedLen ad msg = do aead <- initAEAD @AES256 aesKey ivBytes - msg' <- paddedMsg + msg' <- liftEither $ pad msg paddedLen return $ AES.aeadSimpleEncrypt aead ad msg' authTagSize - where - len = B.length msg - paddedMsg - | len > paddedSize = throwE CryptoLargeMsgError - | otherwise = return (msg <> B.replicate (paddedSize - len) '#') -- | AEAD-GCM decryption with empty associated data. -- @@ -934,7 +813,23 @@ decryptAES key iv = decryptAEAD key iv "" decryptAEAD :: Key -> IV -> ByteString -> ByteString -> AES.AuthTag -> ExceptT CryptoError IO ByteString decryptAEAD aesKey ivBytes ad msg authTag = do aead <- initAEAD @AES256 aesKey ivBytes - maybeError AESDecryptError $ AES.aeadSimpleDecrypt aead ad msg authTag + liftEither . unPad =<< maybeError AESDecryptError (AES.aeadSimpleDecrypt aead ad msg authTag) + +pad :: ByteString -> Int -> Either CryptoError ByteString +pad msg paddedLen + | padLen >= 0 = Right $ encodeWord16 (fromIntegral len) <> msg <> B.replicate padLen '#' + | otherwise = Left CryptoLargeMsgError + where + len = B.length msg + padLen = paddedLen - len - 2 + +unPad :: ByteString -> Either CryptoError ByteString +unPad padded + | B.length rest >= len = Right $ B.take len rest + | otherwise = Left CryptoLargeMsgError + where + (lenWrd, rest) = B.splitAt 2 padded + len = fromIntegral $ decodeWord16 lenWrd initAEAD :: forall c. AES.BlockCipher c => Key -> IV -> ExceptT CryptoError IO (AES.AEAD c) initAEAD (Key aesKey) (IV ivBytes) = do @@ -971,33 +866,10 @@ bsToAuthTag = AES.AuthTag . BA.pack . map c2w . B.unpack cryptoFailable :: CE.CryptoFailable a -> ExceptT CryptoError IO a cryptoFailable = liftEither . first AESCipherError . CE.eitherCryptoError -oaepParams :: OAEP.OAEPParams SHA256 ByteString ByteString -oaepParams = OAEP.defaultOAEPParams SHA256 - --- | RSA OAEP encryption. --- --- Used as part of hybrid E2E encryption scheme and for SMP transport handshake. -encryptOAEP :: PublicKey RSA -> ByteString -> ExceptT CryptoError IO ByteString -encryptOAEP (PublicKeyRSA k) aesKey = - liftEitherError RSAEncryptError $ - OAEP.encrypt oaepParams k aesKey - --- | RSA OAEP decryption. --- --- Used as part of hybrid E2E encryption scheme and for SMP transport handshake. -decryptOAEP :: PrivateKey RSA -> ByteString -> ExceptT CryptoError IO ByteString -decryptOAEP (PrivateKeyRSA pk) encKey = - liftEitherError RSADecryptError $ - OAEP.decryptSafer oaepParams pk encKey - -pssParams :: PSS.PSSParams SHA256 ByteString ByteString -pssParams = PSS.defaultPSSParams SHA256 - -- | Message signing. -- -- Used by SMP clients to sign SMP commands and by SMP agents to sign messages. sign' :: SignatureAlgorithm a => PrivateKey a -> ByteString -> ExceptT CryptoError IO (Signature a) -sign' (PrivateKeyRSA pk) msg = ExceptT $ bimap RSASignError SignatureRSA <$> PSS.signSafer pssParams pk msg sign' (PrivateKeyEd25519 pk k) msg = pure . SignatureEd25519 $ Ed25519.sign pk k msg sign' (PrivateKeyEd448 pk k) msg = pure . SignatureEd448 $ Ed448.sign pk k msg @@ -1008,7 +880,6 @@ sign (APrivateSignKey a k) = fmap (ASignature a) . sign' k -- -- Used by SMP servers to authorize SMP commands and by SMP agents to verify messages. verify' :: SignatureAlgorithm a => PublicKey a -> Signature a -> ByteString -> Bool -verify' (PublicKeyRSA k) (SignatureRSA sig) msg = PSS.verify pssParams k msg sig verify' (PublicKeyEd25519 k) (SignatureEd25519 sig) msg = Ed25519.verify k msg sig verify' (PublicKeyEd448 k) (SignatureEd448 sig) msg = Ed448.verify k msg sig @@ -1022,31 +893,41 @@ dh' (PublicKeyX25519 k) (PrivateKeyX25519 pk) = DhSecretX25519 $ X25519.dh k pk dh' (PublicKeyX448 k) (PrivateKeyX448 pk) = DhSecretX448 $ X448.dh k pk -- | NaCl @crypto_box@ encrypt with a shared DH secret and 192-bit nonce. -cbEncrypt :: DhSecret X25519 -> ByteString -> ByteString -> ByteString -cbEncrypt secret nonce msg = BA.convert tag `B.append` c +cbEncrypt :: DhSecret X25519 -> CbNonce -> ByteString -> Int -> Either CryptoError ByteString +cbEncrypt secret (CbNonce nonce) msg paddedLen = cryptoBox <$> pad msg paddedLen where - (rs, c) = xSalsa20 secret nonce msg - tag = Poly1305.auth rs c + cryptoBox s = BA.convert tag `B.append` c + where + (rs, c) = xSalsa20 secret nonce s + tag = Poly1305.auth rs c -- | NaCl @crypto_box@ decrypt with a shared DH secret and 192-bit nonce. -cbDecrypt :: DhSecret X25519 -> ByteString -> ByteString -> Either CryptoError ByteString -cbDecrypt secret nonce packet +cbDecrypt :: DhSecret X25519 -> CbNonce -> ByteString -> Either CryptoError ByteString +cbDecrypt secret (CbNonce nonce) packet | B.length packet < 16 = Left CBDecryptError - | BA.constEq tag' tag = Right msg + | BA.constEq tag' tag = unPad msg | otherwise = Left CBDecryptError where (tag', c) = B.splitAt 16 packet (rs, msg) = xSalsa20 secret nonce c tag = Poly1305.auth rs c -cbNonce :: ByteString -> ByteString +newtype CbNonce = CbNonce {unCbNonce :: ByteString} + +cbNonce :: ByteString -> CbNonce cbNonce s - | len == 24 = s - | len > 24 = fst $ B.splitAt 24 s - | otherwise = s <> B.replicate (24 - len) (toEnum 0) + | len == 24 = CbNonce s + | len > 24 = CbNonce . fst $ B.splitAt 24 s + | otherwise = CbNonce $ s <> B.replicate (24 - len) (toEnum 0) where len = B.length s +randomCbNonce :: IO CbNonce +randomCbNonce = CbNonce <$> getRandomBytes 24 + +cbNonceP :: Parser CbNonce +cbNonceP = CbNonce <$> A.take 24 + xSalsa20 :: DhSecret X25519 -> ByteString -> ByteString -> (ByteString, ByteString) xSalsa20 (DhSecretX25519 shared) nonce msg = (rs, msg') where @@ -1057,39 +938,8 @@ xSalsa20 (DhSecretX25519 shared) nonce msg = (rs, msg') (rs, state2) = XSalsa.generate state1 32 (msg', _) = XSalsa.combine state2 msg -pubVerifyKey :: APublicKey -> Either String APublicVerifyKey -pubVerifyKey (APublicKey a k) = case signatureAlgorithm a of - Just Dict -> Right $ APublicVerifyKey a k - _ -> Left "key does not support signature algorithms" - -pubEncryptKey :: APublicKey -> Either String APublicEncryptKey -pubEncryptKey (APublicKey a k) = case encryptionAlgorithm a of - Just Dict -> Right $ APublicEncryptKey a k - _ -> Left "key does not support encryption algorithms" - -pubKey' :: forall a. AlgorithmI a => APublicKey -> Either String (PublicKey a) -pubKey' (APublicKey a k) = case testEquality a $ sAlgorithm @a of - Just Refl -> Right k - _ -> Left "bad key algorithm" - -privSignKey :: APrivateKey -> Either String APrivateSignKey -privSignKey (APrivateKey a k) = case signatureAlgorithm a of - Just Dict -> Right $ APrivateSignKey a k - _ -> Left "key does not support signature algorithms" - -privDecryptKey :: APrivateKey -> Either String APrivateDecryptKey -privDecryptKey (APrivateKey a k) = case encryptionAlgorithm a of - Just Dict -> Right $ APrivateDecryptKey a k - _ -> Left "key does not support encryption algorithms" - -privKey' :: forall a. AlgorithmI a => APrivateKey -> Either String (PrivateKey a) -privKey' (APrivateKey a k) = case testEquality a $ sAlgorithm @a of - Just Refl -> Right k - _ -> Left "bad key algorithm" - publicToX509 :: PublicKey a -> PubKey publicToX509 = \case - PublicKeyRSA k -> PubKeyRSA k PublicKeyEd25519 k -> PubKeyEd25519 k PublicKeyEd448 k -> PubKeyEd448 k PublicKeyX25519 k -> PubKeyX25519 k @@ -1097,7 +947,6 @@ publicToX509 = \case privateToX509 :: PrivateKey a -> PrivKey privateToX509 = \case - PrivateKeyRSA k -> PrivKeyRSA k PrivateKeyEd25519 k _ -> PrivKeyEd25519 k PrivateKeyEd448 k _ -> PrivKeyEd448 k PrivateKeyX25519 k -> PrivKeyX25519 k @@ -1110,7 +959,6 @@ encodeASNObj k = toStrict . encodeASN1 DER $ toASN1 k [] decodePubKey :: ByteString -> Either String APublicKey decodePubKey = decodeKey >=> \case - (PubKeyRSA k, []) -> Right . APublicKey SRSA $ PublicKeyRSA k (PubKeyEd25519 k, []) -> Right . APublicKey SEd25519 $ PublicKeyEd25519 k (PubKeyEd448 k, []) -> Right . APublicKey SEd448 $ PublicKeyEd448 k (PubKeyX25519 k, []) -> Right . APublicKey SX25519 $ PublicKeyX25519 k @@ -1121,7 +969,6 @@ decodePubKey = decodePrivKey :: ByteString -> Either String APrivateKey decodePrivKey = decodeKey >=> \case - (PrivKeyRSA pk, []) -> Right . APrivateKey SRSA $ PrivateKeyRSA pk (PrivKeyEd25519 k, []) -> Right . APrivateKey SEd25519 . PrivateKeyEd25519 k $ Ed25519.toPublic k (PrivKeyEd448 k, []) -> Right . APrivateKey SEd448 . PrivateKeyEd448 k $ Ed448.toPublic k (PrivKeyX25519 k, []) -> Right . APrivateKey SX25519 $ PrivateKeyX25519 k diff --git a/src/Simplex/Messaging/Crypto/Ratchet.hs b/src/Simplex/Messaging/Crypto/Ratchet.hs index 26bfe70ce..9eb19726b 100644 --- a/src/Simplex/Messaging/Crypto/Ratchet.hs +++ b/src/Simplex/Messaging/Crypto/Ratchet.hs @@ -22,11 +22,11 @@ import qualified Data.ByteString.Char8 as B import Data.Map.Strict (Map) import qualified Data.Map.Strict as M import Data.Maybe (fromMaybe) -import Data.Word (Word16, Word32) -import Network.Transport.Internal (decodeWord16, decodeWord32, encodeWord16, encodeWord32) +import Data.Word (Word32) +import Network.Transport.Internal (encodeWord16, encodeWord32) import Simplex.Messaging.Crypto -import Simplex.Messaging.Parsers (parseAll, parseE, parseE') -import Simplex.Messaging.Util (tryE, (<$?>)) +import Simplex.Messaging.Parsers (parseE, parseE', word16P, word32P) +import Simplex.Messaging.Util (tryE) data Ratchet a = Ratchet { -- current ratchet version @@ -77,7 +77,7 @@ newtype RatchetKey = RatchetKey ByteString initSndRatchet' :: forall a. (AlgorithmI a, DhAlgorithm a) => PublicKey a -> PrivateKey a -> ByteString -> ByteString -> IO (Ratchet a) initSndRatchet' rcDHRr sPKey salt rcAD = do - rcDHRs@(_, pk) <- generateKeyPair' @a 0 + rcDHRs@(_, pk) <- generateKeyPair' @a let (sk, rcHKs, rcNHKr) = initKdf salt rcDHRr sPKey -- state.RK, state.CKs, state.NHKs = KDF_RK_HE(SK, DH(state.DHRs, state.DHRr)) (rcRK, rcCKs, rcNHKs) = rootKdf sk rcDHRr pk @@ -128,8 +128,7 @@ data MsgHeader a = MsgHeader msgLatestVersion :: E2EEncryptionVersion, msgDHRs :: PublicKey a, msgPN :: Word32, - msgNs :: Word32, - msgLen :: Word16 + msgNs :: Word32 } deriving (Eq, Show) @@ -145,30 +144,21 @@ fullHeaderLen :: Int fullHeaderLen = paddedHeaderLen + authTagSize + ivSize @AES256 serializeMsgHeader' :: AlgorithmI a => MsgHeader a -> ByteString -serializeMsgHeader' MsgHeader {msgVersion, msgLatestVersion, msgDHRs, msgPN, msgNs, msgLen} = +serializeMsgHeader' MsgHeader {msgVersion, msgLatestVersion, msgDHRs, msgPN, msgNs} = encodeWord16 msgVersion <> encodeWord16 msgLatestVersion - <> encodeWord16 (fromIntegral $ B.length key) - <> key + <> encodeLenKey msgDHRs <> encodeWord32 msgPN <> encodeWord32 msgNs - <> encodeWord16 msgLen - where - key = encodeKey msgDHRs msgHeaderP' :: AlgorithmI a => Parser (MsgHeader a) msgHeaderP' = do - msgVersion <- word16 - msgLatestVersion <- word16 - keyLen <- fromIntegral <$> word16 - msgDHRs <- parseAll binaryKeyP <$?> A.take keyLen - msgPN <- word32 - msgNs <- word32 - msgLen <- word16 - pure MsgHeader {msgVersion, msgLatestVersion, msgDHRs, msgPN, msgNs, msgLen} - where - word16 = decodeWord16 <$> A.take 2 - word32 = decodeWord32 <$> A.take 4 + msgVersion <- word16P + msgLatestVersion <- word16P + msgDHRs <- binaryLenKeyP + msgPN <- word32P + msgNs <- word32P + pure MsgHeader {msgVersion, msgLatestVersion, msgDHRs, msgPN, msgNs} data EncHeader = EncHeader { ehBody :: ByteString, @@ -229,8 +219,7 @@ rcEncrypt' rc@Ratchet {rcSnd = Just sr@SndRatchet {rcCKs, rcHKs}, rcNs, rcAD} pa msgLatestVersion = currentE2EVersion, msgDHRs = fst $ rcDHRs rc, msgPN = rcPN rc, - msgNs = rcNs, - msgLen = fromIntegral $ B.length msg + msgNs = rcNs } data SkippedMessage a @@ -267,7 +256,7 @@ rcDecrypt' rc@Ratchet {rcRcv, rcMKSkipped, rcAD} msg' = do SMMessage msg rc' -> pure (msg, rc') where decryptRcMessage :: RatchetStep -> MsgHeader a -> EncMessage -> ExceptT CryptoError IO (DecryptResult a) - decryptRcMessage rcStep hdr@MsgHeader {msgDHRs, msgPN, msgNs} encMsg = do + decryptRcMessage rcStep MsgHeader {msgDHRs, msgPN, msgNs} encMsg = do -- if dh_ratchet: rc' <- ratchetStep rcStep case skipMessageKeys msgNs rc' of @@ -276,7 +265,7 @@ rcDecrypt' rc@Ratchet {rcRcv, rcMKSkipped, rcAD} msg' = do -- state.CKr, mk = KDF_CK(state.CKr) let (rcCKr', mk, iv, _) = chainKdf rcCKr -- return DECRYPT (mk, ciphertext, CONCAT (AD, enc_header)) - msg <- decryptMessage (MessageKey mk iv) hdr encMsg + msg <- decryptMessage (MessageKey mk iv) encMsg -- state . Nr += 1 pure (msg, rc'' {rcRcv = Just rr {rcCKr = rcCKr'}, rcNr = rcNr + 1}) Right rc'' -> pure (Left CERatchetState, rc'') @@ -289,7 +278,7 @@ rcDecrypt' rc@Ratchet {rcRcv, rcMKSkipped, rcAD} msg' = do Left e -> throwE e Right rc'@Ratchet {rcDHRs, rcRK, rcNHKs, rcNHKr} -> do -- DHRatchetHE(state, header) - rcDHRs' <- liftIO $ generateKeyPair' @a 0 + rcDHRs' <- liftIO $ generateKeyPair' @a -- state.RK, state.CKr, state.NHKr = KDF_RK_HE(state.RK, DH(state.DHRs, state.DHRr)) let (rcRK', rcCKr', rcNHKr') = rootKdf rcRK msgDHRs (snd rcDHRs) -- state.RK, state.CKs, state.NHKs = KDF_RK_HE(state.RK, DH(state.DHRs, state.DHRr)) @@ -349,7 +338,7 @@ rcDecrypt' rc@Ratchet {rcRcv, rcMKSkipped, rcAD} msg' = do | M.null mks' = M.delete hk rcMKSkipped | otherwise = M.insert hk mks' rcMKSkipped rc' = rc {rcMKSkipped = mksSkipped} - msg <- decryptMessage mk hdr encMsg + msg <- decryptMessage mk encMsg pure $ SMMessage msg rc' tryDecryptSkipped r _ = pure r decryptRcHeader :: Maybe RcvRatchet -> EncHeader -> ExceptT CryptoError IO (RatchetStep, MsgHeader a) @@ -364,11 +353,11 @@ rcDecrypt' rc@Ratchet {rcRcv, rcMKSkipped, rcAD} msg' = do decryptHeader k EncHeader {ehBody, ehAuthTag, ehIV} = do header <- decryptAEAD k ehIV rcAD ehBody ehAuthTag `catchE` \_ -> throwE CERatchetHeader parseE' CryptoHeaderError msgHeaderP' header - decryptMessage :: MessageKey -> MsgHeader a -> EncMessage -> ExceptT CryptoError IO (Either CryptoError ByteString) - decryptMessage (MessageKey mk iv) MsgHeader {msgLen} EncMessage {emHeader, emBody, emAuthTag} = + decryptMessage :: MessageKey -> EncMessage -> ExceptT CryptoError IO (Either CryptoError ByteString) + decryptMessage (MessageKey mk iv) EncMessage {emHeader, emBody, emAuthTag} = -- DECRYPT(mk, ciphertext, CONCAT(AD, enc_header)) -- TODO add associated data - tryE (B.take (fromIntegral msgLen) <$> decryptAEAD mk iv (rcAD <> emHeader) emBody emAuthTag) + tryE $ decryptAEAD mk iv (rcAD <> emHeader) emBody emAuthTag initKdf :: (AlgorithmI a, DhAlgorithm a) => ByteString -> PublicKey a -> PrivateKey a -> (RatchetKey, Key, Key) initKdf salt k pk = diff --git a/src/Simplex/Messaging/Parsers.hs b/src/Simplex/Messaging/Parsers.hs index d14419bf0..fb2c88b52 100644 --- a/src/Simplex/Messaging/Parsers.hs +++ b/src/Simplex/Messaging/Parsers.hs @@ -15,10 +15,12 @@ import Data.Char (isAlphaNum) import Data.Time.Clock (UTCTime) import Data.Time.ISO8601 (parseISO8601) import Data.Typeable (Typeable) +import Data.Word (Word16, Word32) import Database.SQLite.Simple (ResultError (..), SQLData (..)) import Database.SQLite.Simple.FromField (FieldParser, returnError) import Database.SQLite.Simple.Internal (Field (..)) import Database.SQLite.Simple.Ok (Ok (Ok)) +import Network.Transport.Internal (decodeWord16, decodeWord32) import Simplex.Messaging.Util ((<$?>)) import Text.Read (readMaybe) @@ -48,6 +50,12 @@ rawBase64UriP = A.takeWhile1 (\c -> isAlphaNum c || c == '-' || c == '_') tsISO8601P :: Parser UTCTime tsISO8601P = maybe (fail "timestamp") pure . parseISO8601 . B.unpack =<< A.takeTill wordEnd +word16P :: Parser Word16 +word16P = decodeWord16 <$> A.take 2 + +word32P :: Parser Word32 +word32P = decodeWord32 <$> A.take 4 + parse :: Parser a -> e -> (ByteString -> Either e a) parse parser err = first (const err) . parseAll parser diff --git a/src/Simplex/Messaging/Protocol.hs b/src/Simplex/Messaging/Protocol.hs index 80d6b183e..fcfe4e349 100644 --- a/src/Simplex/Messaging/Protocol.hs +++ b/src/Simplex/Messaging/Protocol.hs @@ -27,7 +27,12 @@ -- -- See https://github.com/simplex-chat/simplexmq/blob/master/protocol/simplex-messaging.md module Simplex.Messaging.Protocol - ( -- * SMP protocol types + ( -- * SMP protocol parameters + clientVersion, + maxMessageLength, + e2eEncMessageLength, + + -- * SMP protocol types Command (..), CommandI (..), Party (..), @@ -43,6 +48,10 @@ module Simplex.Messaging.Protocol SignedTransmission, SentRawTransmission, SignedRawTransmission, + EncMessage (..), + PubHeader (..), + ClientMessage (..), + PrivHeader (..), CorrId (..), QueueId, RecipientId, @@ -65,6 +74,10 @@ module Simplex.Messaging.Protocol serializeErrorType, transmissionP, errorTypeP, + serializeEncMessage, + encMessageP, + serializeClientMessage, + clientMessageP, -- * TCP transport functions tPut, @@ -91,15 +104,26 @@ import Data.String import Data.Time.Clock import Data.Time.ISO8601 import Data.Type.Equality +import Data.Word (Word16) import GHC.Generics (Generic) import GHC.TypeLits (ErrorMessage (..), TypeError) import Generic.Random (genericArbitraryU) +import Network.Transport.Internal (encodeWord16) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Parsers import Simplex.Messaging.Transport (THandle (..), Transport, TransportError (..), tGetBlock, tPutBlock) import Simplex.Messaging.Util import Test.QuickCheck (Arbitrary (..)) +clientVersion :: Word16 +clientVersion = 1 + +maxMessageLength :: Int +maxMessageLength = 15968 + +e2eEncMessageLength :: Int +e2eEncMessageLength = 15842 + -- | SMP protocol participants. data Party = Broker | Recipient | Sender | Notifier deriving (Show) @@ -227,6 +251,59 @@ isClient = \case SNotifier -> Just Dict _ -> Nothing +-- | SMP message body format +data EncMessage = EncMessage + { emHeader :: PubHeader, + emNonce :: C.CbNonce, + emBody :: ByteString + } + +data PubHeader = PubHeader + { phVersion :: Word16, + phE2ePubDhKey :: C.PublicKeyX25519 + } + +serializePubHeader :: PubHeader -> ByteString +serializePubHeader (PubHeader v k) = encodeWord16 v <> C.encodeLenKey' k + +pubHeaderP :: Parser PubHeader +pubHeaderP = PubHeader <$> word16P <*> C.binaryLenKeyP + +serializeEncMessage :: EncMessage -> ByteString +serializeEncMessage EncMessage {emHeader, emNonce, emBody} = + serializePubHeader emHeader <> C.unCbNonce emNonce <> emBody + +encMessageP :: Parser EncMessage +encMessageP = do + emHeader <- pubHeaderP + emNonce <- C.cbNonceP + emBody <- A.takeByteString + pure EncMessage {emHeader, emNonce, emBody} + +data ClientMessage = ClientMessage PrivHeader ByteString + +data PrivHeader + = PHConfirmation C.APublicVerifyKey + | PHEmpty + +serializePrivHeader :: PrivHeader -> ByteString +serializePrivHeader = \case + PHConfirmation k -> "K" <> C.encodeLenKey k + PHEmpty -> " " + +privHeaderP :: Parser PrivHeader +privHeaderP = + A.anyChar >>= \case + 'K' -> PHConfirmation <$> C.binaryLenKeyP + ' ' -> pure PHEmpty + _ -> fail "invalid PrivHeader" + +serializeClientMessage :: ClientMessage -> ByteString +serializeClientMessage (ClientMessage h msg) = serializePrivHeader h <> msg + +clientMessageP :: Parser ClientMessage +clientMessageP = ClientMessage <$> privHeaderP <*> A.takeByteString + -- | Base-64 encoded string. type Encoded = ByteString @@ -240,7 +317,7 @@ instance IsString CorrId where data QueueIdsKeys = QIK { rcvId :: RecipientId, sndId :: SenderId, - rcvPublicDHKey :: RcvPublicDhKey + rcvPublicDhKey :: RcvPublicDhKey } deriving (Eq, Show) @@ -253,10 +330,10 @@ type RcvPrivateSignKey = C.APrivateSignKey type RcvPublicVerifyKey = C.APublicVerifyKey -- | Public key used for DH exchange to encrypt message bodies from server to recipient -type RcvPublicDhKey = C.PublicKey C.X25519 +type RcvPublicDhKey = C.PublicKeyX25519 -- | DH Secret used to encrypt message bodies from server to recipient -type RcvDhSecret = C.DhSecret C.X25519 +type RcvDhSecret = C.DhSecretX25519 -- | Sender's private key used by the recipient to authorize (sign) SMP commands. -- @@ -292,6 +369,8 @@ data ErrorType QUOTA | -- | ACK command is sent without message to be acknowledged NO_MSG + | -- | sent message is too large (> maxMessageLength = 15968 bytes) + LARGE_MSG | -- | internal server error INTERNAL | -- | used internally, never returned by the server (to be removed) @@ -302,8 +381,6 @@ data ErrorType data CommandError = -- | server response sent from client or vice versa PROHIBITED - | -- | bad RSA key size in NEW or KEY commands (only 1024, 2048 and 4096 bits keys are allowed) - KEY_SIZE | -- | error parsing command SYNTAX | -- | transmission has no required credentials (signature or queue ID) @@ -322,8 +399,7 @@ instance Arbitrary CommandError where arbitrary = genericArbitraryU transmissionP :: Parser RawTransmission transmissionP = do signature <- segment - len <- A.decimal <* A.space - signed <- A.take len <* A.space + signed <- A.takeByteString either fail pure $ parseAll (trn signature signed) signed where segment = A.takeTill (== ' ') <* A.space @@ -356,20 +432,17 @@ instance CommandI Cmd where <|> "ERR " *> serverError <|> "PONG" $> Cmd SBroker PONG where - newCmd = Cmd SRecipient <$> (NEW <$> C.strKeyP <* A.space <*> C.strKeyP) + newCmd = Cmd SRecipient <$> (NEW <$> C.strPubKeyP <* A.space <*> C.strPubKeyP) idsResp = Cmd SBroker . IDS <$> qik - qik = QIK <$> base64P <* A.space <*> base64P <* A.space <*> C.strKeyP + qik = QIK <$> base64P <* A.space <*> base64P <* A.space <*> C.strPubKeyP nIdsResp = Cmd SBroker . NID <$> base64P - keyCmd = Cmd SRecipient . KEY <$> C.strKeyP - nKeyCmd = Cmd SRecipient . NKEY <$> C.strKeyP - sendCmd = do - size <- A.decimal <* A.space - Cmd SSender . SEND <$> A.take size <* A.space + keyCmd = Cmd SRecipient . KEY <$> C.strPubKeyP + nKeyCmd = Cmd SRecipient . NKEY <$> C.strPubKeyP + sendCmd = Cmd SSender . SEND <$> A.takeByteString message = do msgId <- base64P <* A.space ts <- tsISO8601P <* A.space - size <- A.decimal <* A.space - Cmd SBroker . MSG msgId ts <$> A.take size <* A.space + Cmd SBroker . MSG msgId ts <$> A.takeByteString serverError = Cmd SBroker . ERR <$> errorTypeP instance CommandI ClientCmd where @@ -393,20 +466,20 @@ instance PartyI p => CommandI (Command p) where Just Refl -> Right cmd _ -> Left "bad command party" serializeCommand = \case - NEW rKey dhKey -> B.unwords ["NEW", C.serializeKey rKey, C.serializeKey dhKey] - KEY sKey -> "KEY " <> C.serializeKey sKey - NKEY nKey -> "NKEY " <> C.serializeKey nKey + NEW rKey dhKey -> B.unwords ["NEW", C.serializePubKey rKey, C.serializePubKey' dhKey] + KEY sKey -> "KEY " <> C.serializePubKey sKey + NKEY nKey -> "NKEY " <> C.serializePubKey nKey SUB -> "SUB" ACK -> "ACK" OFF -> "OFF" DEL -> "DEL" - SEND msgBody -> "SEND " <> serializeBody msgBody + SEND msgBody -> "SEND " <> msgBody PING -> "PING" NSUB -> "NSUB" MSG msgId ts msgBody -> - B.unwords ["MSG", encode msgId, B.pack $ formatISO8601Millis ts, serializeBody msgBody] + B.unwords ["MSG", encode msgId, B.pack $ formatISO8601Millis ts, msgBody] IDS (QIK rcvId sndId srvDh) -> - B.unwords ["IDS", encode rcvId, encode sndId, C.serializeKey srvDh] + B.unwords ["IDS", encode rcvId, encode sndId, C.serializePubKey' srvDh] NID nId -> "NID " <> encode nId ERR err -> "ERR " <> serializeErrorType err NMSG -> "NMSG" @@ -414,9 +487,6 @@ instance PartyI p => CommandI (Command p) where OK -> "OK" PONG -> "PONG" -serializeBody :: ByteString -> ByteString -serializeBody s = bshow (B.length s) <> " " <> s <> " " - -- | SMP error parser. errorTypeP :: Parser ErrorType errorTypeP = "CMD " *> (CMD <$> parseRead1) <|> parseRead1 @@ -427,7 +497,7 @@ serializeErrorType = bshow -- | Send signed SMP transmission to TCP transport. tPut :: Transport c => THandle c -> SentRawTransmission -> IO (Either TransportError ()) -tPut th (sig, t) = tPutBlock th $ C.serializeSignature sig <> " " <> serializeBody t +tPut th (sig, t) = tPutBlock th $ C.serializeSignature sig <> " " <> t serializeTransmission :: CommandI c => ByteString -> Transmission c -> ByteString serializeTransmission sessionId (CorrId corrId, queueId, command) = @@ -447,7 +517,9 @@ fromServer = \case -- | Receive and parse transmission from the TCP transport (ignoring any trailing padding). tGetParse :: Transport c => THandle c -> IO (Either TransportError RawTransmission) -tGetParse th = first (const TEBadBlock) . A.parseOnly transmissionP <$> tGetBlock th +tGetParse th = (parseTransmission =<<) <$> tGetBlock th + where + parseTransmission = first (const TEBadBlock) . A.parseOnly transmissionP -- | Receive client and server transmissions. -- diff --git a/src/Simplex/Messaging/Server.hs b/src/Simplex/Messaging/Server.hs index 56082e2c9..4f854a38e 100644 --- a/src/Simplex/Messaging/Server.hs +++ b/src/Simplex/Messaging/Server.hs @@ -118,9 +118,8 @@ runSMPServerBlocking started cfg@ServerConfig {transports} = do atomically . stateTVar (clientSubs c) $ \ss -> (M.lookup qId ss, M.delete qId ss) runClient :: (Transport c, MonadUnliftIO m, MonadReader Env m) => TProxy c -> c -> m () -runClient _ h = do - ServerConfig {blockSize} <- asks config - liftIO (runExceptT $ serverHandshake h blockSize) >>= \case +runClient _ h = + liftIO (runExceptT $ serverHandshake h) >>= \case Right th -> runClientTransport th Left _ -> pure () @@ -202,12 +201,6 @@ verifyTransmission sig_ signed queueId cmd = do -- by having the same time of the response whether a queue exists or nor, for all valid key/signature sizes dummyPublicKey :: C.Signature a -> C.PublicKey a dummyPublicKey = \case - C.SignatureRSA s' -> case B.length s' of - 128 -> dummyKey128 - 256 -> dummyKey256 - 384 -> dummyKey384 - 512 -> dummyKey512 - _ -> dummyKey256 C.SignatureEd25519 _ -> dummyKeyEd25519 C.SignatureEd448 _ -> dummyKeyEd448 @@ -217,18 +210,6 @@ dummyKeyEd25519 = "MCowBQYDK2VwAyEA139Oqs4QgpqbAmB0o7rZf6T19ryl7E65k4AYe0kE3Qs=" dummyKeyEd448 :: C.PublicKey 'C.Ed448 dummyKeyEd448 = "MEMwBQYDK2VxAzoA6ibQc9XpkSLtwrf7PLvp81qW/etiumckVFImCMRdftcG/XopbOSaq9qyLhrgJWKOLyNrQPNVvpMA" -dummyKey128 :: C.PublicKey 'C.RSA -dummyKey128 = "MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKBgQC2oeA7s4roXN5K2N6022I1/2CTeMKjWH0m00bSZWa4N8LDKeFcShh8YUxZea5giAveViTRNOOVLgcuXbKvR3u24szN04xP0+KnYUuUUIIoT3YSjX0IlomhDhhSyup4BmA0gAZ+D1OaIKZFX6J8yQ1Lr/JGLEfSRsBjw8l+4hs9OwKBgQDKA+YlZvGb3BcpDwKmatiCXN7ZRDWkjXbj8VAW5zV95tSRCCVN48hrFM1H4Ju2QMMUc6kPUVX+eW4ZjdCl5blIqIHMcTmsdcmsDDCg3PjUNrwc6bv/1TcirbAKcmnKt9iurIt6eerxSO7TZUXXMUVsi7eRwb/RUNhpCrpJ/hpIOw==" - -dummyKey256 :: C.PublicKey 'C.RSA -dummyKey256 = "MIIBoDANBgkqhkiG9w0BAQEFAAOCAY0AMIIBiAKCAQEAxwmTvaqmdTbkfUGNi8Yu0L/T4cxuOlQlx3zGZ9X9Qx0+oZjknWK+QHrdWTcpS+zH4Hi7fP6kanOQoQ90Hj6Ghl57VU1GEdUPywSw4i1/7t0Wv9uT9Q2ktHp2rqVo3xkC9IVIpL7EZAxdRviIN2OsOB3g4a/F1ZpjxcAaZeOMUugiAX1+GtkLuE0Xn4neYjCaOghLxQTdhybN70VtnkiQLx/X9NjkDIl/spYGm3tQFMyYKkP6IWoEpj0926hJ0fmlmhy8tAOhlZsb/baW5cgkEZ3E9jVVrySCgQzoLQgma610FIISRpRJbSyv26jU7MkMxiyuBiDaFOORkXFttoKbtQKBgEbDS9II2brsz+vfI7uP8atFcawkE52cx4M1UWQhqb1H3tBiRl+qO+dMq1pPQF2bW7dlZAWYzS4W/367bTAuALHBDGB8xi1P4Njhh9vaOgTvuqrHG9NJQ85BLy0qGw8rjIWSIXVmVpfrXFJ8po5l04UE258Ll2yocv3QRQmddQW9" - -dummyKey384 :: C.PublicKey 'C.RSA -dummyKey384 = "MIICITANBgkqhkiG9w0BAQEFAAOCAg4AMIICCQKCAYEAthExp77lSFBMB0RedjgKIU+oNH5lMGdMqDCG0E5Ly7X49rFpfDMMN08GDIgvzg9kcwV3ScbPcjUE19wmAShX9f9k3w38KM3wmIBKSiuCREQl0V3xAYp1SYwiAkMNSSwxuIkDEeSOR56WdEcZvqbB4lY9MQlUv70KriPDxZaqKCTKslUezXHQuYPQX6eMnGFK7hxz5Kl5MajV52d+5iXsa8CA+m/e1KVnbelCO+xhN89xG8ALt0CJ9k5Wwo3myLgXi4dmNankCmg8jkh+7y2ywkzxMwH1JydDtV/FLzkbZsbPR2w93TNrTq1RJOuqMyh0VtdBSpxNW/Ft988TkkX2BAWzx82INw7W6/QbHGNtHNB995R4sgeYy8QbEpNGBhQnfQh7yRWygLTVXWKApQzzfCeIoDDWUS7dMv/zXoasAnpDBj+6UhHv3BHrps7kBvRyZQ2d/nUuAqiGd43ljJ++n6vNyFLgZoiV7HLia/FOGMkdt7j92CNmFHxiT6Xl7kRHAoGBAPNoWny2O7LBxzAKMLmQVHBAiKp6RMx+7URvtQDHDHPaZ7F3MvtvmYWwGzund3cQFAaV1EkJoYeI3YRuj6xdXgMyMaP54On++btArb6jUtZuvlC98qE8dEEHQNh+7TsCiMU+ivbeKFxS9A/B7OVedoMnPoJWhatbA9zB/6L1GNPh" - -dummyKey512 :: C.PublicKey 'C.RSA -dummyKey512 = "MIICoDANBgkqhkiG9w0BAQEFAAOCAo0AMIICiAKCAgEArkCY9DuverJ4mmzDektv9aZMFyeRV46WZK9NsOBKEc+1ncqMs+LhLti9asKNgUBRbNzmbOe0NYYftrUpwnATaenggkTFxxbJ4JGJuGYbsEdFWkXSvrbWGtM8YUmn5RkAGme12xQ89bSM4VoJAGnrYPHwmcQd+KYCPZvTUsxaxgrJTX65ejHN9BsAn8XtGViOtHTDJO9yUMD2WrJvd7wnNa+0ugEteDLzMU++xS98VC+uA1vfauUqi3yXVchdfrLdVUuM+JE0gUEXCgzjuHkaoHiaGNiGhdPYoAJJdOKQOIHAKdk7Th6OPhirPhc9XYNB4O8JDthKhNtfokvFIFlC4QBRzJhpLIENaEBDt08WmgpOnecZB/CuxkqqOrNa8j5K5jNrtXAI67W46VEC2jeQy/gZwb64Zit2A4D00xXzGbQTPGj4ehcEMhLx5LSCygViEf0w0tN3c3TEyUcgPzvECd2ZVpQLr9Z4a07Ebr+YSuxcHhjg4Rg1VyJyOTTvaCBGm5X2B3+tI4NUttmikIHOYpBnsLmHY2BgfH2KcrIsDyAhInXmTFr/L2+erFarUnlfATd2L8Ti43TNHDedO6k6jI5Gyi62yPwjqPLEIIK8l+pIeNfHJ3pPmjhHBfzFcQLMMMXffHWNK8kWklrQXK+4j4HiPcTBvlO1FEtG9nEIZhUCgYA4a6WtI2k5YNli1C89GY5rGUY7RP71T6RWri/D3Lz9T7GvU+FemAyYmsvCQwqijUOur0uLvwSP8VdxpSUcrjJJSWur2hrPWzWlu0XbNaeizxpFeKbQP+zSrWJ1z8RwfAeUjShxt8q1TuqGqY10wQyp3nyiTGvS+KwZVj5h5qx8NQ==" - client :: forall m. (MonadUnliftIO m, MonadReader Env m) => Client -> Server -> m () client clnt@Client {subscriptions, ntfSubscriptions, rcvQ, sndQ} Server {subscribedQ, ntfSubscribedQ, notifiers} = forever $ @@ -256,10 +237,10 @@ client clnt@Client {subscriptions, ntfSubscriptions, rcvQ, sndQ} Server {subscri DEL -> delQueueAndMsgs st where createQueue :: QueueStore -> RcvPublicVerifyKey -> RcvPublicDhKey -> m BrokerTransmission - createQueue st recipientKey dhKey = checkKeySize recipientKey $ do - (rcvPublicDHKey, privDhKey) <- liftIO $ C.generateKeyPair' 0 + createQueue st recipientKey dhKey = do + (rcvPublicDhKey, privDhKey) <- liftIO C.generateKeyPair' let rcvDhSecret = C.dh' dhKey privDhKey - qik (rcvId, sndId) = QIK {rcvId, sndId, rcvPublicDHKey} + qik (rcvId, sndId) = QIK {rcvId, sndId, rcvPublicDhKey} qRec (recipientId, senderId) = QueueRec { recipientId, @@ -270,7 +251,7 @@ client clnt@Client {subscriptions, ntfSubscriptions, rcvQ, sndQ} Server {subscri notifier = Nothing, status = QueueActive } - addQueueRetry 3 qik qRec + (corrId,queueId,) <$> addQueueRetry 3 qik qRec where addQueueRetry :: Int -> ((RecipientId, SenderId) -> QueueIdsKeys) -> ((RecipientId, SenderId) -> QueueRec) -> m (Command 'Broker) @@ -299,10 +280,10 @@ client clnt@Client {subscriptions, ntfSubscriptions, rcvQ, sndQ} Server {subscri secureQueue_ :: QueueStore -> SndPublicVerifyKey -> m BrokerTransmission secureQueue_ st sKey = do withLog $ \s -> logSecureQueue s queueId sKey - atomically . checkKeySize sKey $ either ERR (const OK) <$> secureQueue st queueId sKey + atomically $ (corrId,queueId,) . either ERR (const OK) <$> secureQueue st queueId sKey addQueueNotifier_ :: QueueStore -> NtfPublicVerifyKey -> m BrokerTransmission - addQueueNotifier_ st nKey = checkKeySize nKey $ addNotifierRetry 3 + addQueueNotifier_ st nKey = (corrId,queueId,) <$> addNotifierRetry 3 where addNotifierRetry :: Int -> m (Command 'Broker) addNotifierRetry 0 = pure $ ERR INTERNAL @@ -315,13 +296,6 @@ client clnt@Client {subscriptions, ntfSubscriptions, rcvQ, sndQ} Server {subscri withLog $ \s -> logAddNotifier s queueId nId nKey pure $ NID nId - checkKeySize :: Monad m' => C.APublicVerifyKey -> m' (Command 'Broker) -> m' BrokerTransmission - checkKeySize key action = - (corrId,queueId,) - <$> if C.validKeySize key - then action - else pure . ERR $ CMD KEY_SIZE - suspendQueue_ :: QueueStore -> m BrokerTransmission suspendQueue_ st = do withLog (`logDeleteQueue` queueId) @@ -361,30 +335,34 @@ client clnt@Client {subscriptions, ntfSubscriptions, rcvQ, sndQ} Server {subscri withSub rId f = readTVar subscriptions >>= mapM f . M.lookup rId sendMessage :: QueueStore -> MsgBody -> m BrokerTransmission - sendMessage st msgBody = do - qr <- atomically $ getQueue st (CP SSender) queueId - either (return . err) storeMessage qr + sendMessage st msgBody + | B.length msgBody > maxMessageLength = pure $ err LARGE_MSG + | otherwise = do + qr <- atomically $ getQueue st (CP SSender) queueId + either (return . err) storeMessage qr where storeMessage :: QueueRec -> m BrokerTransmission storeMessage qr = case status qr of QueueOff -> return $ err AUTH - QueueActive -> do - ms <- asks msgStore - msg <- mkMessage - quota <- asks $ msgQueueQuota . config - atomically $ do - q <- getMsgQueue ms (recipientId qr) quota - ifM (isFull q) (pure $ err QUOTA) $ do - trySendNotification - writeMsg q msg - pure ok + QueueActive -> + mkMessage >>= \case + Left _ -> pure $ err LARGE_MSG + Right msg -> do + ms <- asks msgStore + quota <- asks $ msgQueueQuota . config + atomically $ do + q <- getMsgQueue ms (recipientId qr) quota + ifM (isFull q) (pure $ err QUOTA) $ do + trySendNotification + writeMsg q msg + pure ok where - mkMessage :: m Message + mkMessage :: m (Either C.CryptoError Message) mkMessage = do msgId <- randomId =<< asks (msgIdBytes . config) ts <- liftIO getCurrentTime - let c = C.cbEncrypt (rcvDhSecret qr) (C.cbNonce msgId) msgBody - return $ Message {msgId, ts, msgBody = c} + let c = C.cbEncrypt (rcvDhSecret qr) (C.cbNonce msgId) msgBody (maxMessageLength + 2) + pure $ Message msgId ts <$> c trySendNotification :: STM () trySendNotification = diff --git a/src/Simplex/Messaging/Server/Env/STM.hs b/src/Simplex/Messaging/Server/Env/STM.hs index 501b8987f..22b710282 100644 --- a/src/Simplex/Messaging/Server/Env/STM.hs +++ b/src/Simplex/Messaging/Server/Env/STM.hs @@ -31,7 +31,6 @@ data ServerConfig = ServerConfig queueIdBytes :: Int, msgIdBytes :: Int, storeLog :: Maybe (StoreLog 'ReadMode), - blockSize :: Int, caCertificateFile :: FilePath, serverPrivateKeyFile :: FilePath, serverCertificateFile :: FilePath diff --git a/src/Simplex/Messaging/Server/StoreLog.hs b/src/Simplex/Messaging/Server/StoreLog.hs index 05cb2be50..d2208e560 100644 --- a/src/Simplex/Messaging/Server/StoreLog.hs +++ b/src/Simplex/Messaging/Server/StoreLog.hs @@ -62,36 +62,36 @@ storeLogRecordP = <|> "DELETE " *> (DeleteQueue <$> base64P) where createQueueP = CreateQueue <$> queueRecP - secureQueueP = SecureQueue <$> base64P <* A.space <*> C.strKeyP + secureQueueP = SecureQueue <$> base64P <* A.space <*> C.strPubKeyP addNotifierP = - AddNotifier <$> base64P <* A.space <*> base64P <* A.space <*> C.strKeyP + AddNotifier <$> base64P <* A.space <*> base64P <* A.space <*> C.strPubKeyP queueRecP = do recipientId <- "rid=" *> base64P - recipientKey <- " rk=" *> C.strKeyP + recipientKey <- " rk=" *> C.strPubKeyP rcvDhSecret <- " rdh=" *> C.strDhSecretP senderId <- " sid=" *> base64P - senderKey <- " sk=" *> optional C.strKeyP - notifier <- optional $ (,) <$> (" nid=" *> base64P) <*> (" nk=" *> C.strKeyP) + senderKey <- " sk=" *> optional C.strPubKeyP + notifier <- optional $ (,) <$> (" nid=" *> base64P) <*> (" nk=" *> C.strPubKeyP) pure QueueRec {recipientId, recipientKey, rcvDhSecret, senderId, senderKey, notifier, status = QueueActive} serializeStoreLogRecord :: StoreLogRecord -> ByteString serializeStoreLogRecord = \case CreateQueue q -> "CREATE " <> serializeQueue q - SecureQueue rId sKey -> "SECURE " <> encode rId <> " " <> C.serializeKey sKey - AddNotifier rId nId nKey -> B.unwords ["NOTIFIER", encode rId, encode nId, C.serializeKey nKey] + SecureQueue rId sKey -> "SECURE " <> encode rId <> " " <> C.serializePubKey sKey + AddNotifier rId nId nKey -> B.unwords ["NOTIFIER", encode rId, encode nId, C.serializePubKey nKey] DeleteQueue rId -> "DELETE " <> encode rId where serializeQueue QueueRec {recipientId, recipientKey, rcvDhSecret, senderId, senderKey, notifier} = B.unwords [ "rid=" <> encode recipientId, - "rk=" <> C.serializeKey recipientKey, + "rk=" <> C.serializePubKey recipientKey, "rdh=" <> C.serializeDhSecret rcvDhSecret, "sid=" <> encode senderId, - "sk=" <> maybe "" C.serializeKey senderKey + "sk=" <> maybe "" C.serializePubKey senderKey ] <> maybe "" serializeNotifier notifier - serializeNotifier (nId, nKey) = " nid=" <> encode nId <> " nk=" <> C.serializeKey nKey + serializeNotifier (nId, nKey) = " nid=" <> encode nId <> " nk=" <> C.serializePubKey nKey openWriteStoreLog :: FilePath -> IO (StoreLog 'WriteMode) openWriteStoreLog f = WriteStoreLog f <$> openFile f WriteMode diff --git a/src/Simplex/Messaging/Transport.hs b/src/Simplex/Messaging/Transport.hs index 2539dec48..bbcb72cc1 100644 --- a/src/Simplex/Messaging/Transport.hs +++ b/src/Simplex/Messaging/Transport.hs @@ -25,7 +25,10 @@ -- -- See https://github.com/simplex-chat/simplexmq/blob/master/protocol/simplex-messaging.md#appendix-a module Simplex.Messaging.Transport - ( -- * Transport connection class + ( -- * SMP transport parameters + smpBlockSize, + + -- * Transport connection class Transport (..), TProxy (..), ATransport (..), @@ -67,6 +70,7 @@ import qualified Crypto.Store.X509 as SX import Data.Attoparsec.ByteString.Char8 (Parser) import qualified Data.Attoparsec.ByteString.Char8 as A import Data.Bifunctor (first) +import Data.Bitraversable (bimapM) import Data.ByteString.Base64 import Data.ByteString.Char8 (ByteString) import qualified Data.ByteString.Char8 as B @@ -97,6 +101,9 @@ import UnliftIO.Exception (Exception, IOException) import qualified UnliftIO.Exception as E import UnliftIO.STM +smpBlockSize :: Int +smpBlockSize = 16384 + -- * Transport connection class class Transport c where @@ -395,8 +402,7 @@ smpVersionP = -- | The handle for SMP encrypted transport connection over Transport . data THandle c = THandle { connection :: c, - sessionId :: ByteString, - blockSize :: Int + sessionId :: ByteString } data Handshake = Handshake @@ -453,25 +459,23 @@ serializeTransportError = \case -- | Pad and send block to SMP transport. tPutBlock :: Transport c => THandle c -> ByteString -> IO (Either TransportError ()) -tPutBlock THandle {connection = c, blockSize} block - | len > blockSize = pure $ Left TELargeMsg - | otherwise = Right <$> cPut c (block <> B.replicate (blockSize - len) '#') - where - len = B.length block +tPutBlock THandle {connection = c} block = + bimapM (const $ pure TELargeMsg) (cPut c) $ + C.pad block smpBlockSize -- | Receive block from SMP transport. -tGetBlock :: Transport c => THandle c -> IO ByteString -tGetBlock THandle {connection = c, blockSize} = - cGet c blockSize >>= \case +tGetBlock :: Transport c => THandle c -> IO (Either TransportError ByteString) +tGetBlock THandle {connection = c} = + cGet c smpBlockSize >>= \case "" -> ioe_EOF - msg -> pure msg + msg -> pure . first (const TELargeMsg) $ C.unPad msg -- | Server SMP transport handshake. -- -- See https://github.com/simplex-chat/simplexmq/blob/master/protocol/simplex-messaging.md#appendix-a -serverHandshake :: Transport c => c -> Int -> ExceptT TransportError IO (THandle c) -serverHandshake c blockSize = do - let th@THandle {sessionId} = tHandle c blockSize +serverHandshake :: Transport c => c -> ExceptT TransportError IO (THandle c) +serverHandshake c = do + let th@THandle {sessionId} = tHandle c _ <- getPeerHello th sendHelloToPeer th sessionId pure th @@ -479,9 +483,9 @@ serverHandshake c blockSize = do -- | Client SMP transport handshake. -- -- See https://github.com/simplex-chat/simplexmq/blob/master/protocol/simplex-messaging.md#appendix-a -clientHandshake :: forall c. Transport c => c -> Int -> ExceptT TransportError IO (THandle c) -clientHandshake c blockSize = do - let th@THandle {sessionId} = tHandle c blockSize +clientHandshake :: forall c. Transport c => c -> ExceptT TransportError IO (THandle c) +clientHandshake c = do + let th@THandle {sessionId} = tHandle c sendHelloToPeer th "" Handshake {sessionId = sessId} <- getPeerHello th if sessionId == sessId @@ -494,11 +498,11 @@ sendHelloToPeer th sessionId = in ExceptT . tPutBlock th $ serializeHandshake handshake getPeerHello :: Transport c => THandle c -> ExceptT TransportError IO Handshake -getPeerHello th = ExceptT $ parseHandshake <$> tGetBlock th +getPeerHello th = ExceptT $ (parseHandshake =<<) <$> tGetBlock th where parseHandshake :: ByteString -> Either TransportError Handshake parseHandshake = first (const $ TEHandshake PARSE) . A.parseOnly handshakeP -tHandle :: Transport c => c -> Int -> THandle c -tHandle c blockSize = - THandle {connection = c, sessionId = encode $ tlsUnique c, blockSize} +tHandle :: Transport c => c -> THandle c +tHandle c = + THandle {connection = c, sessionId = encode $ tlsUnique c} diff --git a/tests/AgentTests.hs b/tests/AgentTests.hs index 23855700a..39e537940 100644 --- a/tests/AgentTests.hs +++ b/tests/AgentTests.hs @@ -14,8 +14,6 @@ import AgentTests.DoubleRatchetTests (doubleRatchetTests) import AgentTests.FunctionalAPITests (functionalAPITests) import AgentTests.SQLiteTests (storeTests) import Control.Concurrent -import Data.ByteString.Base64 (decodeLenient) -import qualified Data.ByteString.Base64.URL as U import Data.ByteString.Char8 (ByteString) import qualified Data.ByteString.Char8 as B import Network.HTTP.Types (urlEncode) @@ -354,6 +352,9 @@ connect (h1, name1) (h2, name2) = do samplePublicKey :: ByteString samplePublicKey = "rsa:MIIBoDANBgkqhkiG9w0BAQEFAAOCAY0AMIIBiAKCAQEAtn1NI2tPoOGSGfad0aUg0tJ0kG2nzrIPGLiz8wb3dQSJC9xkRHyzHhEE8Kmy2cM4q7rNZIlLcm4M7oXOTe7SC4x59bLQG9bteZPKqXu9wk41hNamV25PWQ4zIcIRmZKETVGbwN7jFMpH7wxLdI1zzMArAPKXCDCJ5ctWh4OWDI6OR6AcCtEj-toCI6N6pjxxn5VigJtwiKhxYpoUJSdNM60wVEDCSUrZYBAuDH8pOxPfP-Tm4sokaFDTIG3QJFzOjC-_9nW4MUjAOFll9PCp9kaEFHJ_YmOYKMWNOCCPvLS6lxA83i0UaardkNLNoFS5paWfTlroxRwOC2T6PwO2ywKBgDjtXcSED61zK1seocQMyGRINnlWdhceD669kIHju_f6kAayvYKW3_lbJNXCmyinAccBosO08_0sUxvtuniIo18kfYJE0UmP1ReCjhMP-O-yOmwZJini_QelJk_Pez8IIDDWnY1qYQsN_q7ocjakOYrpGG7mig6JMFpDJtD6istR" +sampleDhKey :: ByteString +sampleDhKey = "x25519:MCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o=" + syntaxTests :: forall c. Transport c => TProxy c -> Spec syntaxTests t = do it "unknown command" $ ("1", "5678", "HELLO") >#> ("1", "5678", "ERR CMD SYNTAX") @@ -370,7 +371,16 @@ syntaxTests t = do -- TODO: ERROR no connection alias in the response (it does not generate it yet if not provided) -- TODO: add tests with defined connection alias it "using same server as in invitation" $ - ("311", "a", "JOIN https://simpex.chat/invitation#/?smp=smp%3A%2F%2F" <> (U.encode . decodeLenient) "9VjLsOY5ZvB4hoglNdBzJFAUi/vP4GkZnJFahQOXV20=" <> "%40localhost%3A5001%2F3456-w%3D%3D%23&e2e=" <> urlEncode True samplePublicKey <> " 14\nbob's connInfo") + ( "311", + "a", + "JOIN https://simpex.chat/invitation#/?smp=smp%3A%2F%2F" + <> urlEncode True "9VjLsOY5ZvB4hoglNdBzJFAUi_vP4GkZnJFahQOXV20=" + <> "%40localhost%3A5001%2F3456-w%3D%3D%23" + <> urlEncode True sampleDhKey + <> "&e2e=" + <> urlEncode True samplePublicKey + <> " 14\nbob's connInfo" + ) >#> ("311", "a", "ERR SMP AUTH") describe "invalid" $ do -- TODO: JOIN is not merged yet - to be added diff --git a/tests/AgentTests/ConnectionRequestTests.hs b/tests/AgentTests/ConnectionRequestTests.hs index 0bfb09b2e..821e88c5a 100644 --- a/tests/AgentTests/ConnectionRequestTests.hs +++ b/tests/AgentTests/ConnectionRequestTests.hs @@ -1,9 +1,11 @@ +{-# LANGUAGE DataKinds #-} {-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} module AgentTests.ConnectionRequestTests where -import qualified Crypto.PubKey.RSA as R +import Data.ByteString (ByteString) +import Network.HTTP.Types (urlEncode) import Simplex.Messaging.Agent.Protocol import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Parsers (parseAll) @@ -24,9 +26,19 @@ queue :: SMPQueueUri queue = SMPQueueUri { smpServer = srv, - senderId = "\223\142z\251" + senderId = "\223\142z\251", + dhPublicKey = testDhKey } +testDhKey :: C.PublicKeyX25519 +testDhKey = "MCowBQYDK2VuAyEAjiswwI3O/NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o=" + +testDhKeyStr :: ByteString +testDhKeyStr = C.serializePubKeyUri' testDhKey + +testDhKeyStrUri :: ByteString +testDhKeyStrUri = urlEncode True testDhKeyStr + appServer :: ConnReqScheme appServer = CRSAppServer "simplex.chat" Nothing @@ -36,7 +48,7 @@ connectionRequest = ConnReqData { crScheme = appServer, crSmpQueues = [queue], - crEncryptKey = C.APublicEncryptKey C.SRSA (C.PublicKeyRSA $ R.PublicKey 1 0 0) + crEncryption = ConnectionEncryption } connectionRequestTests :: Spec @@ -44,17 +56,24 @@ connectionRequestTests = describe "connection request parsing / serializing" $ do it "should serialize SMP queue URIs" $ do serializeSMPQueueUri queue {smpServer = srv {port = Nothing}} - `shouldBe` "smp://1234-w==@smp.simplex.im/3456-w==#" + `shouldBe` "smp://1234-w==@smp.simplex.im/3456-w==#" <> testDhKeyStr serializeSMPQueueUri queue - `shouldBe` "smp://1234-w==@smp.simplex.im:5223/3456-w==#" + `shouldBe` "smp://1234-w==@smp.simplex.im:5223/3456-w==#" <> testDhKeyStr it "should parse SMP queue URIs" $ do - parseAll smpQueueUriP "smp://1234-w==@smp.simplex.im/3456-w==#" + parseAll smpQueueUriP ("smp://1234-w==@smp.simplex.im/3456-w==#" <> testDhKeyStr) `shouldBe` Right queue {smpServer = srv {port = Nothing}} - parseAll smpQueueUriP "smp://1234-w==@smp.simplex.im:5223/3456-w==#" + parseAll smpQueueUriP ("smp://1234-w==@smp.simplex.im:5223/3456-w==#" <> testDhKeyStr) `shouldBe` Right queue it "should serialize connection requests" $ do serializeConnReq connectionRequest - `shouldBe` "https://simplex.chat/invitation#/?smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23&e2e=rsa%3AMBowDQYJKoZIhvcNAQEBBQADCQAwBgIBAAIBAA%3D%3D" + `shouldBe` "https://simplex.chat/invitation#/?smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23" + <> testDhKeyStrUri + <> "&e2e=" it "should parse connection requests" $ do - parseAll connReqP "https://simplex.chat/invitation#/?smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23&e2e=rsa%3AMBowDQYJKoZIhvcNAQEBBQADCQAwBgIBAAIBAA%3D%3D" + parseAll + connReqP + ( "https://simplex.chat/invitation#/?smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23" + <> testDhKeyStrUri + <> "&e2e=" + ) `shouldBe` Right connectionRequest diff --git a/tests/AgentTests/DoubleRatchetTests.hs b/tests/AgentTests/DoubleRatchetTests.hs index 16d62dd0f..5f8c24433 100644 --- a/tests/AgentTests/DoubleRatchetTests.hs +++ b/tests/AgentTests/DoubleRatchetTests.hs @@ -43,8 +43,8 @@ fullMsgLen = fullHeaderLen + paddedMsgLen + C.authTagSize testMessageHeader :: Expectation testMessageHeader = do - (k, _) <- C.generateKeyPair' @X25519 0 - let hdr = MsgHeader {msgVersion = 1, msgLatestVersion = 1, msgDHRs = k, msgPN = 0, msgNs = 0, msgLen = 11} + (k, _) <- C.generateKeyPair' @X25519 + let hdr = MsgHeader {msgVersion = 1, msgLatestVersion = 1, msgDHRs = k, msgPN = 0, msgNs = 0} parseAll (msgHeaderP' @X25519) (serializeMsgHeader' hdr) `shouldBe` Right hdr pattern Decrypted :: ByteString -> Either CryptoError (Either CryptoError ByteString) @@ -152,8 +152,8 @@ withRatchets test = do initRatchets :: (AlgorithmI a, DhAlgorithm a) => IO (Ratchet a, Ratchet a) initRatchets = do salt <- getRandomBytes 16 - (ak, apk) <- C.generateKeyPair' 0 - (bk, bpk) <- C.generateKeyPair' 0 + (ak, apk) <- C.generateKeyPair' + (bk, bpk) <- C.generateKeyPair' bob <- initSndRatchet' ak bpk salt "bob -> alice" alice <- initRcvRatchet' bk (ak, apk) salt "bob -> alice" pure (alice, bob) diff --git a/tests/AgentTests/SQLiteTests.hs b/tests/AgentTests/SQLiteTests.hs index f3969932b..c9cd7f7f6 100644 --- a/tests/AgentTests/SQLiteTests.hs +++ b/tests/AgentTests/SQLiteTests.hs @@ -12,7 +12,6 @@ import Control.Concurrent.Async (concurrently_) import Control.Concurrent.STM import Control.Monad (replicateM_) import Control.Monad.Except (ExceptT, runExceptT) -import qualified Crypto.PubKey.RSA as R import Crypto.Random (drgNew) import Data.ByteString.Char8 (ByteString) import qualified Data.Text as T @@ -150,38 +149,17 @@ cData1 :: ConnData cData1 = ConnData {connId = "conn1"} testPrivateSignKey :: C.APrivateSignKey -testPrivateSignKey = C.APrivateSignKey C.SRSA testPrivateKey +testPrivateSignKey = C.APrivateSignKey C.SEd25519 "MC4CAQAwBQYDK2VwBCIEIDfEfevydXXfKajz3sRkcQ7RPvfWUPoq6pu1TYHV1DEe" -testPrivateDecryptKey :: C.APrivateDecryptKey -testPrivateDecryptKey = C.APrivateDecryptKey C.SRSA testPrivateKey +testPubDhKey :: C.PublicKeyX25519 +testPubDhKey = "MCowBQYDK2VuAyEAjiswwI3O/NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o=" -testPublicEncryptKey :: C.APublicEncryptKey -testPublicEncryptKey = C.APublicEncryptKey C.SRSA testPublicKey +testPrivDhKey :: C.PrivateKeyX25519 +testPrivDhKey = "MC4CAQAwBQYDK2VuBCIEINCzbVFaCiYHoYncxNY8tSIfn0pXcIAhLBfFc0m+gOpk" -testPublicKey :: C.PublicKey 'C.RSA -testPublicKey = C.PublicKeyRSA $ R.PublicKey 1 2 3 - -testDhSecret :: C.DhSecret 'C.X25519 +testDhSecret :: C.DhSecretX25519 testDhSecret = "01234567890123456789012345678901" -testPrivateKey :: C.PrivateKey 'C.RSA -testPrivateKey = - C.PrivateKeyRSA - R.PrivateKey - { private_pub = - R.PublicKey - { public_size = 1, - public_n = 2, - public_e = 0 - }, - private_d = 3, - private_p = 0, - private_q = 0, - private_dP = 0, - private_dQ = 0, - private_qinv = 0 - } - rcvQueue1 :: RcvQueue rcvQueue1 = RcvQueue @@ -189,9 +167,9 @@ rcvQueue1 = rcvId = "1234", rcvPrivateKey = testPrivateSignKey, rcvDhSecret = testDhSecret, + e2ePrivKey = testPrivDhKey, + e2eShared = Nothing, sndId = Just "2345", - decryptKey = testPrivateDecryptKey, - verifyKey = Nothing, status = New } @@ -201,8 +179,8 @@ sndQueue1 = { server = SMPServer "smp.simplex.im" (Just "5223") testKeyHash, sndId = "3456", sndPrivateKey = testPrivateSignKey, - encryptKey = testPublicEncryptKey, - signKey = testPrivateSignKey, + e2ePubKey = testPubDhKey, + e2eDhSecret = testDhSecret, status = New } @@ -341,8 +319,8 @@ testUpgradeRcvConnToDuplex = { server = SMPServer "smp.simplex.im" (Just "5223") testKeyHash, sndId = "2345", sndPrivateKey = testPrivateSignKey, - encryptKey = testPublicEncryptKey, - signKey = testPrivateSignKey, + e2ePubKey = testPubDhKey, + e2eDhSecret = testDhSecret, status = New } upgradeRcvConnToDuplex store "conn1" anotherSndQueue @@ -362,9 +340,9 @@ testUpgradeSndConnToDuplex = rcvId = "3456", rcvPrivateKey = testPrivateSignKey, rcvDhSecret = testDhSecret, + e2ePrivKey = testPrivDhKey, + e2eShared = Nothing, sndId = Just "4567", - decryptKey = testPrivateDecryptKey, - verifyKey = Nothing, status = New } upgradeSndConnToDuplex store "conn1" anotherRcvQueue @@ -440,7 +418,7 @@ mkRcvMsgData internalId internalRcvId externalSndId brokerId internalHash = MsgMeta { integrity = MsgOk, recipient = (unId internalId, ts), - sender = (externalSndId, ts), + sndMsgId = externalSndId, broker = (brokerId, ts) }, msgBody = hw, @@ -474,7 +452,7 @@ mkSndMsgData internalId internalSndId internalHash = internalTs = ts, msgBody = hw, internalHash, - previousMsgHash = internalHash + prevMsgHash = internalHash } testCreateSndMsg' :: SQLiteStore -> PrevSndMsgHash -> ConnId -> SndMsgData -> Expectation diff --git a/tests/SMPClient.hs b/tests/SMPClient.hs index 7fafa023b..06f1bd99d 100644 --- a/tests/SMPClient.hs +++ b/tests/SMPClient.hs @@ -40,9 +40,6 @@ testPort2 = "5002" testKeyHashStr :: ByteString testKeyHashStr = "9VjLsOY5ZvB4hoglNdBzJFAUi/vP4GkZnJFahQOXV20=" -testBlockSize :: Int -testBlockSize = 16 * 1024 -- TODO move to Protocol - testKeyHash :: Maybe C.KeyHash testKeyHash = Just "9VjLsOY5ZvB4hoglNdBzJFAUi/vP4GkZnJFahQOXV20=" @@ -52,7 +49,7 @@ testStoreLogFile = "tests/tmp/smp-server-store.log" testSMPClient :: (Transport c, MonadUnliftIO m) => (THandle c -> m a) -> m a testSMPClient client = runTransportClient testHost testPort testKeyHash $ \h -> - liftIO (runExceptT $ clientHandshake h testBlockSize) >>= \case + liftIO (runExceptT $ clientHandshake h) >>= \case Right th -> client th Left e -> error $ show e @@ -66,7 +63,6 @@ cfg = queueIdBytes = 24, msgIdBytes = 24, storeLog = Nothing, - blockSize = testBlockSize, caCertificateFile = "tests/fixtures/ca.crt", serverPrivateKeyFile = "tests/fixtures/server.key", serverCertificateFile = "tests/fixtures/server.crt" diff --git a/tests/ServerTests.hs b/tests/ServerTests.hs index 7733d44fe..acfc93ca9 100644 --- a/tests/ServerTests.hs +++ b/tests/ServerTests.hs @@ -69,13 +69,13 @@ testCreateSecure :: ATransport -> Spec testCreateSecure (ATransport t) = it "should create (NEW) and secure (KEY) queue" $ smpTest t $ \h -> do - (rPub, rKey) <- C.generateSignatureKeyPair 0 C.SEd448 - (dhPub, dhPriv :: C.PrivateKey 'C.X25519) <- C.generateKeyPair' 0 - Resp "abcd" rId1 (Ids rId sId srvDh) <- signSendRecv h rKey ("abcd", "", B.unwords ["NEW", C.serializeKey rPub, C.serializeKey dhPub]) - let dec = C.cbDecrypt $ C.dh' srvDh dhPriv + (rPub, rKey) <- C.generateSignatureKeyPair C.SEd448 + (dhPub, dhPriv :: C.PrivateKeyX25519) <- C.generateKeyPair' + Resp "abcd" rId1 (Ids rId sId srvDh) <- signSendRecv h rKey ("abcd", "", B.unwords ["NEW", C.serializePubKey rPub, C.serializePubKey dhPub]) + let dec nonce = C.cbDecrypt (C.dh' srvDh dhPriv) (C.cbNonce nonce) (rId1, "") #== "creates queue" - Resp "bcda" sId1 ok1 <- sendRecv h ("", "bcda", sId, "SEND 5 hello ") + Resp "bcda" sId1 ok1 <- sendRecv h ("", "bcda", sId, "SEND hello") (ok1, OK) #== "accepts unsigned SEND" (sId1, sId) #== "same queue ID in response 1" @@ -88,12 +88,12 @@ testCreateSecure (ATransport t) = Resp "dabc" _ err6 <- signSendRecv h rKey ("dabc", rId, "ACK") (err6, ERR NO_MSG) #== "replies ERR when message acknowledged without messages" - (sPub, sKey) <- C.generateSignatureKeyPair 0 C.SEd448 - Resp "abcd" sId2 err1 <- signSendRecv h sKey ("abcd", sId, "SEND 5 hello ") + (sPub, sKey) <- C.generateSignatureKeyPair C.SEd448 + Resp "abcd" sId2 err1 <- signSendRecv h sKey ("abcd", sId, "SEND hello") (err1, ERR AUTH) #== "rejects signed SEND" (sId2, sId) #== "same queue ID in response 2" - let keyCmd = "KEY " <> C.serializeKey sPub + let keyCmd = "KEY " <> C.serializePubKey sPub Resp "bcda" _ err2 <- sendRecv h (sampleSig, "bcda", rId, keyCmd) (err2, ERR AUTH) #== "rejects KEY with wrong signature" @@ -107,7 +107,7 @@ testCreateSecure (ATransport t) = Resp "abcd" _ err4 <- signSendRecv h rKey ("abcd", rId, keyCmd) (err4, ERR AUTH) #== "rejects KEY if already secured" - Resp "bcda" _ ok3 <- signSendRecv h sKey ("bcda", sId, "SEND 11 hello again ") + Resp "bcda" _ ok3 <- signSendRecv h sKey ("bcda", sId, "SEND hello again") (ok3, OK) #== "accepts signed SEND" Resp "" _ (MSG mId2 _ msg2) <- tGet fromServer h @@ -116,27 +116,27 @@ testCreateSecure (ATransport t) = Resp "cdab" _ ok5 <- signSendRecv h rKey ("cdab", rId, "ACK") (ok5, OK) #== "replies OK when message acknowledged 2" - Resp "dabc" _ err5 <- sendRecv h ("", "dabc", sId, "SEND 5 hello ") + Resp "dabc" _ err5 <- sendRecv h ("", "dabc", sId, "SEND hello") (err5, ERR AUTH) #== "rejects unsigned SEND" testCreateDelete :: ATransport -> Spec testCreateDelete (ATransport t) = it "should create (NEW), suspend (OFF) and delete (DEL) queue" $ smpTest2 t $ \rh sh -> do - (rPub, rKey) <- C.generateSignatureKeyPair 0 C.SEd25519 - (dhPub, dhPriv :: C.PrivateKey 'C.X25519) <- C.generateKeyPair' 0 - Resp "abcd" rId1 (Ids rId sId srvDh) <- signSendRecv rh rKey ("abcd", "", B.unwords ["NEW", C.serializeKey rPub, C.serializeKey dhPub]) - let dec = C.cbDecrypt $ C.dh' srvDh dhPriv + (rPub, rKey) <- C.generateSignatureKeyPair C.SEd25519 + (dhPub, dhPriv :: C.PrivateKeyX25519) <- C.generateKeyPair' + Resp "abcd" rId1 (Ids rId sId srvDh) <- signSendRecv rh rKey ("abcd", "", B.unwords ["NEW", C.serializePubKey rPub, C.serializePubKey dhPub]) + let dec nonce = C.cbDecrypt (C.dh' srvDh dhPriv) (C.cbNonce nonce) (rId1, "") #== "creates queue" - (sPub, sKey) <- C.generateSignatureKeyPair 0 C.SEd25519 - Resp "bcda" _ ok1 <- signSendRecv rh rKey ("bcda", rId, "KEY " <> C.serializeKey sPub) + (sPub, sKey) <- C.generateSignatureKeyPair C.SEd25519 + Resp "bcda" _ ok1 <- signSendRecv rh rKey ("bcda", rId, "KEY " <> C.serializePubKey sPub) (ok1, OK) #== "secures queue" - Resp "cdab" _ ok2 <- signSendRecv sh sKey ("cdab", sId, "SEND 5 hello ") + Resp "cdab" _ ok2 <- signSendRecv sh sKey ("cdab", sId, "SEND hello") (ok2, OK) #== "accepts signed SEND" - Resp "dabc" _ ok7 <- signSendRecv sh sKey ("dabc", sId, "SEND 7 hello 2 ") + Resp "dabc" _ ok7 <- signSendRecv sh sKey ("dabc", sId, "SEND hello 2") (ok7, OK) #== "accepts signed SEND 2 - this message is not delivered because the first is not ACKed" Resp "" _ (MSG mId1 _ msg1) <- tGet fromServer rh @@ -152,10 +152,10 @@ testCreateDelete (ATransport t) = (ok3, OK) #== "suspends queue" (rId2, rId) #== "same queue ID in response 2" - Resp "dabc" _ err3 <- signSendRecv sh sKey ("dabc", sId, "SEND 5 hello ") + Resp "dabc" _ err3 <- signSendRecv sh sKey ("dabc", sId, "SEND hello") (err3, ERR AUTH) #== "rejects signed SEND" - Resp "abcd" _ err4 <- sendRecv sh ("", "abcd", sId, "SEND 5 hello ") + Resp "abcd" _ err4 <- sendRecv sh ("", "abcd", sId, "SEND hello") (err4, ERR AUTH) #== "reject unsigned SEND too" Resp "bcda" _ ok4 <- signSendRecv rh rKey ("bcda", rId, "OFF") @@ -174,10 +174,10 @@ testCreateDelete (ATransport t) = (ok6, OK) #== "deletes queue" (rId3, rId) #== "same queue ID in response 3" - Resp "cdab" _ err7 <- signSendRecv sh sKey ("cdab", sId, "SEND 5 hello ") + Resp "cdab" _ err7 <- signSendRecv sh sKey ("cdab", sId, "SEND hello") (err7, ERR AUTH) #== "rejects signed SEND when deleted" - Resp "dabc" _ err8 <- sendRecv sh ("", "dabc", sId, "SEND 5 hello ") + Resp "dabc" _ err8 <- sendRecv sh ("", "dabc", sId, "SEND hello") (err8, ERR AUTH) #== "rejects unsigned SEND too when deleted" Resp "abcd" _ err11 <- signSendRecv rh rKey ("abcd", rId, "ACK") @@ -193,10 +193,10 @@ stressTest :: ATransport -> Spec stressTest (ATransport t) = it "should create many queues, disconnect and re-connect" $ smpTest3 t $ \h1 h2 h3 -> do - (rPub, rKey) <- C.generateSignatureKeyPair 0 C.SEd25519 - (dhPub, _ :: C.PrivateKey 'C.X25519) <- C.generateKeyPair' 0 + (rPub, rKey) <- C.generateSignatureKeyPair C.SEd25519 + (dhPub, _ :: C.PrivateKeyX25519) <- C.generateKeyPair' rIds <- forM [1 .. 50 :: Int] . const $ do - Resp "" "" (Ids rId _ _) <- signSendRecv h1 rKey ("", "", B.unwords ["NEW", C.serializeKey rPub, C.serializeKey dhPub]) + Resp "" "" (Ids rId _ _) <- signSendRecv h1 rKey ("", "", B.unwords ["NEW", C.serializePubKey rPub, C.serializePubKey dhPub]) pure rId let subscribeQueues h = forM_ rIds $ \rId -> do Resp "" rId' OK <- signSendRecv h rKey ("", rId, "SUB") @@ -210,26 +210,26 @@ testDuplex :: ATransport -> Spec testDuplex (ATransport t) = it "should create 2 simplex connections and exchange messages" $ smpTest2 t $ \alice bob -> do - (arPub, arKey) <- C.generateSignatureKeyPair 0 C.SEd448 - (aDhPub, aDhPriv :: C.PrivateKey 'C.X25519) <- C.generateKeyPair' 0 - Resp "abcd" _ (Ids aRcv aSnd aSrvDh) <- signSendRecv alice arKey ("abcd", "", B.unwords ["NEW", C.serializeKey arPub, C.serializeKey aDhPub]) - let aDec = C.cbDecrypt $ C.dh' aSrvDh aDhPriv + (arPub, arKey) <- C.generateSignatureKeyPair C.SEd448 + (aDhPub, aDhPriv :: C.PrivateKeyX25519) <- C.generateKeyPair' + Resp "abcd" _ (Ids aRcv aSnd aSrvDh) <- signSendRecv alice arKey ("abcd", "", B.unwords ["NEW", C.serializePubKey arPub, C.serializePubKey aDhPub]) + let aDec nonce = C.cbDecrypt (C.dh' aSrvDh aDhPriv) (C.cbNonce nonce) -- aSnd ID is passed to Bob out-of-band - (bsPub, bsKey) <- C.generateSignatureKeyPair 0 C.SEd448 - Resp "bcda" _ OK <- sendRecv bob ("", "bcda", aSnd, cmdSEND $ "key " <> C.serializeKey bsPub) + (bsPub, bsKey) <- C.generateSignatureKeyPair C.SEd448 + Resp "bcda" _ OK <- sendRecv bob ("", "bcda", aSnd, cmdSEND $ "key " <> C.serializePubKey bsPub) -- "key ..." is ad-hoc, different from SMP protocol Resp "" _ (MSG mId1 _ msg1) <- tGet fromServer alice Resp "cdab" _ OK <- signSendRecv alice arKey ("cdab", aRcv, "ACK") Right ["key", bobKey] <- pure $ B.words <$> aDec mId1 msg1 - (bobKey, C.serializeKey bsPub) #== "key received from Bob" + (bobKey, C.serializePubKey bsPub) #== "key received from Bob" Resp "dabc" _ OK <- signSendRecv alice arKey ("dabc", aRcv, "KEY " <> bobKey) - (brPub, brKey) <- C.generateSignatureKeyPair 0 C.SEd448 - (bDhPub, bDhPriv :: C.PrivateKey 'C.X25519) <- C.generateKeyPair' 0 - Resp "abcd" _ (Ids bRcv bSnd bSrvDh) <- signSendRecv bob brKey ("abcd", "", B.unwords ["NEW", C.serializeKey brPub, C.serializeKey bDhPub]) - let bDec = C.cbDecrypt $ C.dh' bSrvDh bDhPriv + (brPub, brKey) <- C.generateSignatureKeyPair C.SEd448 + (bDhPub, bDhPriv :: C.PrivateKeyX25519) <- C.generateKeyPair' + Resp "abcd" _ (Ids bRcv bSnd bSrvDh) <- signSendRecv bob brKey ("abcd", "", B.unwords ["NEW", C.serializePubKey brPub, C.serializePubKey bDhPub]) + let bDec nonce = C.cbDecrypt (C.dh' bSrvDh bDhPriv) (C.cbNonce nonce) Resp "bcda" _ OK <- signSendRecv bob bsKey ("bcda", aSnd, cmdSEND $ "reply_id " <> encode bSnd) -- "reply_id ..." is ad-hoc, it is not a part of SMP protocol @@ -238,17 +238,17 @@ testDuplex (ATransport t) = Right ["reply_id", bId] <- pure $ B.words <$> aDec mId2 msg2 (bId, encode bSnd) #== "reply queue ID received from Bob" - (asPub, asKey) <- C.generateSignatureKeyPair 0 C.SEd448 - Resp "dabc" _ OK <- sendRecv alice ("", "dabc", bSnd, cmdSEND $ "key " <> C.serializeKey asPub) + (asPub, asKey) <- C.generateSignatureKeyPair C.SEd448 + Resp "dabc" _ OK <- sendRecv alice ("", "dabc", bSnd, cmdSEND $ "key " <> C.serializePubKey asPub) -- "key ..." is ad-hoc, different from SMP protocol Resp "" _ (MSG mId3 _ msg3) <- tGet fromServer bob Resp "abcd" _ OK <- signSendRecv bob brKey ("abcd", bRcv, "ACK") Right ["key", aliceKey] <- pure $ B.words <$> bDec mId3 msg3 - (aliceKey, C.serializeKey asPub) #== "key received from Alice" + (aliceKey, C.serializePubKey asPub) #== "key received from Alice" Resp "bcda" _ OK <- signSendRecv bob brKey ("bcda", bRcv, "KEY " <> aliceKey) - Resp "cdab" _ OK <- signSendRecv bob bsKey ("cdab", aSnd, "SEND 8 hi alice ") + Resp "cdab" _ OK <- signSendRecv bob bsKey ("cdab", aSnd, "SEND hi alice") Resp "" _ (MSG mId4 _ msg4) <- tGet fromServer alice Resp "dabc" _ OK <- signSendRecv alice arKey ("dabc", aRcv, "ACK") @@ -264,11 +264,11 @@ testSwitchSub :: ATransport -> Spec testSwitchSub (ATransport t) = it "should create simplex connections and switch subscription to another TCP connection" $ smpTest3 t $ \rh1 rh2 sh -> do - (rPub, rKey) <- C.generateSignatureKeyPair 0 C.SEd448 - (dhPub, dhPriv :: C.PrivateKey 'C.X25519) <- C.generateKeyPair' 0 - Resp "abcd" _ (Ids rId sId srvDh) <- signSendRecv rh1 rKey ("abcd", "", B.unwords ["NEW", C.serializeKey rPub, C.serializeKey dhPub]) - let dec = C.cbDecrypt $ C.dh' srvDh dhPriv - Resp "bcda" _ ok1 <- sendRecv sh ("", "bcda", sId, "SEND 5 test1 ") + (rPub, rKey) <- C.generateSignatureKeyPair C.SEd448 + (dhPub, dhPriv :: C.PrivateKeyX25519) <- C.generateKeyPair' + Resp "abcd" _ (Ids rId sId srvDh) <- signSendRecv rh1 rKey ("abcd", "", B.unwords ["NEW", C.serializePubKey rPub, C.serializePubKey dhPub]) + let dec nonce = C.cbDecrypt (C.dh' srvDh dhPriv) (C.cbNonce nonce) + Resp "bcda" _ ok1 <- sendRecv sh ("", "bcda", sId, "SEND test1") (ok1, OK) #== "sent test message 1" Resp "cdab" _ ok2 <- sendRecv sh ("", "cdab", sId, cmdSEND "test2, no ACK") (ok2, OK) #== "sent test message 2" @@ -285,7 +285,7 @@ testSwitchSub (ATransport t) = Resp "" _ end <- tGet fromServer rh1 (end, END) #== "unsubscribed the 1st TCP connection" - Resp "dabc" _ OK <- sendRecv sh ("", "dabc", sId, "SEND 5 test3 ") + Resp "dabc" _ OK <- sendRecv sh ("", "dabc", sId, "SEND test3") Resp "" _ (MSG mId3 _ msg3) <- tGet fromServer rh2 (dec mId3 msg3, Right "test3") #== "delivered to the 2nd TCP connection" @@ -303,9 +303,9 @@ testSwitchSub (ATransport t) = testWithStoreLog :: ATransport -> Spec testWithStoreLog at@(ATransport t) = it "should store simplex queues to log and restore them after server restart" $ do - (sPub1, sKey1) <- C.generateSignatureKeyPair 0 C.SEd25519 - (sPub2, sKey2) <- C.generateSignatureKeyPair 0 C.SEd25519 - (nPub, nKey) <- C.generateSignatureKeyPair 0 C.SEd25519 + (sPub1, sKey1) <- C.generateSignatureKeyPair C.SEd25519 + (sPub2, sKey2) <- C.generateSignatureKeyPair C.SEd25519 + (nPub, nKey) <- C.generateSignatureKeyPair C.SEd25519 recipientId1 <- newTVarIO "" recipientKey1 <- newTVarIO Nothing dhShared1 <- newTVarIO Nothing @@ -315,7 +315,7 @@ testWithStoreLog at@(ATransport t) = withSmpServerStoreLogOn at testPort . runTest t $ \h -> runClient t $ \h1 -> do (sId1, rId1, rKey1, dhShared) <- createAndSecureQueue h sPub1 - Resp "abcd" _ (NID nId) <- signSendRecv h rKey1 ("abcd", rId1, "NKEY " <> C.serializeKey nPub) + Resp "abcd" _ (NID nId) <- signSendRecv h rKey1 ("abcd", rId1, "NKEY " <> C.serializePubKey nPub) atomically $ do writeTVar recipientId1 rId1 writeTVar recipientKey1 $ Just rKey1 @@ -323,16 +323,16 @@ testWithStoreLog at@(ATransport t) = writeTVar senderId1 sId1 writeTVar notifierId nId Resp "dabc" _ OK <- signSendRecv h1 nKey ("dabc", nId, "NSUB") - Resp "bcda" _ OK <- signSendRecv h sKey1 ("bcda", sId1, "SEND 5 hello ") + Resp "bcda" _ OK <- signSendRecv h sKey1 ("bcda", sId1, "SEND hello") Resp "" _ (MSG mId1 _ msg1) <- tGet fromServer h - (C.cbDecrypt dhShared mId1 msg1, Right "hello") #== "delivered from queue 1" + (C.cbDecrypt dhShared (C.cbNonce mId1) msg1, Right "hello") #== "delivered from queue 1" Resp "" _ NMSG <- tGet fromServer h1 (sId2, rId2, rKey2, dhShared2) <- createAndSecureQueue h sPub2 atomically $ writeTVar senderId2 sId2 - Resp "cdab" _ OK <- signSendRecv h sKey2 ("cdab", sId2, "SEND 9 hello too ") + Resp "cdab" _ OK <- signSendRecv h sKey2 ("cdab", sId2, "SEND hello too") Resp "" _ (MSG mId2 _ msg2) <- tGet fromServer h - (C.cbDecrypt dhShared2 mId2 msg2, Right "hello too") #== "delivered from queue 2" + (C.cbDecrypt dhShared2 (C.cbNonce mId2) msg2, Right "hello too") #== "delivered from queue 2" Resp "dabc" _ OK <- signSendRecv h rKey2 ("dabc", rId2, "DEL") pure () @@ -342,7 +342,7 @@ testWithStoreLog at@(ATransport t) = withSmpServerThreadOn at testPort . runTest t $ \h -> do sId1 <- readTVarIO senderId1 -- fails if store log is disabled - Resp "bcda" _ (ERR AUTH) <- signSendRecv h sKey1 ("bcda", sId1, "SEND 5 hello ") + Resp "bcda" _ (ERR AUTH) <- signSendRecv h sKey1 ("bcda", sId1, "SEND hello") pure () withSmpServerStoreLogOn at testPort . runTest t $ \h -> runClient t $ \h1 -> do @@ -353,13 +353,13 @@ testWithStoreLog at@(ATransport t) = sId1 <- readTVarIO senderId1 nId <- readTVarIO notifierId Resp "dabc" _ OK <- signSendRecv h1 nKey ("dabc", nId, "NSUB") - Resp "bcda" _ OK <- signSendRecv h sKey1 ("bcda", sId1, "SEND 5 hello ") + Resp "bcda" _ OK <- signSendRecv h sKey1 ("bcda", sId1, "SEND hello") Resp "cdab" _ (MSG mId3 _ msg3) <- signSendRecv h rKey1 ("cdab", rId1, "SUB") - (C.cbDecrypt dh1 mId3 msg3, Right "hello") #== "delivered from restored queue" + (C.cbDecrypt dh1 (C.cbNonce mId3) msg3, Right "hello") #== "delivered from restored queue" Resp "" _ NMSG <- tGet fromServer h1 -- this queue is removed - not restored sId2 <- readTVarIO senderId2 - Resp "cdab" _ (ERR AUTH) <- signSendRecv h sKey2 ("cdab", sId2, "SEND 9 hello too ") + Resp "cdab" _ (ERR AUTH) <- signSendRecv h sKey2 ("cdab", sId2, "SEND hello too") pure () logSize `shouldReturn` 1 @@ -381,11 +381,11 @@ testWithStoreLog at@(ATransport t) = createAndSecureQueue :: Transport c => THandle c -> SndPublicVerifyKey -> IO (SenderId, RecipientId, RcvPrivateSignKey, RcvDhSecret) createAndSecureQueue h sPub = do - (rPub, rKey) <- C.generateSignatureKeyPair 0 C.SEd448 - (dhPub, dhPriv :: C.PrivateKey 'C.X25519) <- C.generateKeyPair' 0 - Resp "abcd" "" (Ids rId sId srvDh) <- signSendRecv h rKey ("abcd", "", B.unwords ["NEW", C.serializeKey rPub, C.serializeKey dhPub]) + (rPub, rKey) <- C.generateSignatureKeyPair C.SEd448 + (dhPub, dhPriv :: C.PrivateKeyX25519) <- C.generateKeyPair' + Resp "abcd" "" (Ids rId sId srvDh) <- signSendRecv h rKey ("abcd", "", B.unwords ["NEW", C.serializePubKey rPub, C.serializePubKey dhPub]) let dhShared = C.dh' srvDh dhPriv - let keyCmd = "KEY " <> C.serializeKey sPub + let keyCmd = "KEY " <> C.serializePubKey sPub Resp "dabc" rId' OK <- signSendRecv h rKey ("dabc", rId, keyCmd) (rId', rId) #== "same queue ID" pure (sId, rId, rKey, dhShared) @@ -398,30 +398,8 @@ testTiming (ATransport t) = (testSameTiming rh sh) [ (32, 32, 200), (32, 57, 100), - (32, 128, 40), - (32, 256, 20), (57, 32, 200), - (57, 57, 100), - (57, 128, 40), - (57, 256, 20), - (128, 32, 200), - (128, 57, 100), - (128, 128, 40), - (128, 256, 20), - (256, 32, 200), - (256, 57, 100), - (256, 128, 40), - (256, 256, 20) - -- (256, 384, 15), - -- (256, 512, 10), - -- (384, 128, 40), - -- (384, 256, 20), - -- (384, 384, 15), - -- (384, 512, 10), - -- (512, 128, 40), - -- (512, 256, 20), - -- (512, 384, 15), - -- (512, 512, 10) + (57, 57, 100) ] where timeRepeat n = fmap fst . timeItT . forM_ (replicate n ()) . const @@ -429,28 +407,28 @@ testTiming (ATransport t) = testSameTiming :: Transport c => THandle c -> THandle c -> (Int, Int, Int) -> Expectation testSameTiming rh sh (goodKeySize, badKeySize, n) = do (rPub, rKey) <- generateKeys goodKeySize - (dhPub, dhPriv :: C.PrivateKey 'C.X25519) <- C.generateKeyPair' 0 - Resp "abcd" "" (Ids rId sId srvDh) <- signSendRecv rh rKey ("abcd", "", B.unwords ["NEW", C.serializeKey rPub, C.serializeKey dhPub]) - let dec = C.cbDecrypt $ C.dh' srvDh dhPriv + (dhPub, dhPriv :: C.PrivateKeyX25519) <- C.generateKeyPair' + Resp "abcd" "" (Ids rId sId srvDh) <- signSendRecv rh rKey ("abcd", "", B.unwords ["NEW", C.serializePubKey rPub, C.serializePubKey dhPub]) + let dec nonce = C.cbDecrypt (C.dh' srvDh dhPriv) (C.cbNonce nonce) Resp "cdab" _ OK <- signSendRecv rh rKey ("cdab", rId, "SUB") (_, badKey) <- generateKeys badKeySize -- runTimingTest rh badKey rId "SUB" (sPub, sKey) <- generateKeys goodKeySize - let keyCmd = "KEY " <> C.serializeKey sPub + let keyCmd = "KEY " <> C.serializePubKey sPub Resp "dabc" _ OK <- signSendRecv rh rKey ("dabc", rId, keyCmd) - Resp "bcda" _ OK <- signSendRecv sh sKey ("bcda", sId, "SEND 5 hello ") + Resp "bcda" _ OK <- signSendRecv sh sKey ("bcda", sId, "SEND hello") Resp "" _ (MSG mId _ msg) <- tGet fromServer rh (dec mId msg, Right "hello") #== "delivered from queue" - runTimingTest sh badKey sId "SEND 5 hello " + runTimingTest sh badKey sId "SEND hello" where generateKeys = \case - 32 -> C.generateSignatureKeyPair 0 C.SEd25519 - 57 -> C.generateSignatureKeyPair 0 C.SEd448 - size -> C.generateSignatureKeyPair size C.SRSA + 32 -> C.generateSignatureKeyPair C.SEd25519 + 57 -> C.generateSignatureKeyPair C.SEd448 + _ -> error "unsupported key size" runTimingTest h badKey qId cmd = do timeWrongKey <- timeRepeat n $ do Resp "cdab" _ (ERR AUTH) <- signSendRecv h badKey ("cdab", qId, cmd) @@ -470,22 +448,23 @@ testTiming (ATransport t) = testMessageNotifications :: ATransport -> Spec testMessageNotifications (ATransport t) = it "should create simplex connection, subscribe notifier and deliver notifications" $ do - (sPub, sKey) <- C.generateSignatureKeyPair 0 C.SEd25519 - (nPub, nKey) <- C.generateSignatureKeyPair 0 C.SEd25519 + (sPub, sKey) <- C.generateSignatureKeyPair C.SEd25519 + (nPub, nKey) <- C.generateSignatureKeyPair C.SEd25519 smpTest4 t $ \rh sh nh1 nh2 -> do (sId, rId, rKey, dhShared) <- createAndSecureQueue rh sPub - Resp "1" _ (NID nId) <- signSendRecv rh rKey ("1", rId, "NKEY " <> C.serializeKey nPub) + let dec nonce = C.cbDecrypt dhShared (C.cbNonce nonce) + Resp "1" _ (NID nId) <- signSendRecv rh rKey ("1", rId, "NKEY " <> C.serializePubKey nPub) Resp "2" _ OK <- signSendRecv nh1 nKey ("2", nId, "NSUB") - Resp "3" _ OK <- signSendRecv sh sKey ("3", sId, "SEND 5 hello ") + Resp "3" _ OK <- signSendRecv sh sKey ("3", sId, "SEND hello") Resp "" _ (MSG mId1 _ msg1) <- tGet fromServer rh - (C.cbDecrypt dhShared mId1 msg1, Right "hello") #== "delivered from queue" + (dec mId1 msg1, Right "hello") #== "delivered from queue" Resp "3a" _ OK <- signSendRecv rh rKey ("3a", rId, "ACK") Resp "" _ NMSG <- tGet fromServer nh1 Resp "4" _ OK <- signSendRecv nh2 nKey ("4", nId, "NSUB") Resp "" _ END <- tGet fromServer nh1 - Resp "5" _ OK <- signSendRecv sh sKey ("5", sId, "SEND 11 hello again ") + Resp "5" _ OK <- signSendRecv sh sKey ("5", sId, "SEND hello again") Resp "" _ (MSG mId2 _ msg2) <- tGet fromServer rh - (C.cbDecrypt dhShared mId2 msg2, Right "hello again") #== "delivered from queue again" + (dec mId2 msg2, Right "hello again") #== "delivered from queue again" Resp "" _ NMSG <- tGet fromServer nh2 1000 `timeout` tGet fromServer nh1 >>= \case Nothing -> return () @@ -498,7 +477,7 @@ sampleDhPubKey :: ByteString sampleDhPubKey = "x25519:MCowBQYDK2VuAyEAriy+HcARIhqsgSjVnjKqoft+y6pxrxdY68zn4+LjYhQ=" sampleSig :: Maybe C.ASignature -sampleSig = "gM8qn2Vx3GkhIp2hgrji9uhfXKpgtKDmc0maxdP8GvbORUxMCTlLG8Q/gNcl3pQVOzmbZqTZZfKcGDn9DaquJ3fT5D/NKdeW//d6ETE1EXsIbpENS0QsS+bKZDjpp3w3eQlfUxn4BNisp2S14CmJBm/FaiNj2fPkLqfkzZALcoY=" +sampleSig = "e8JK+8V3fq6kOLqco/SaKlpNaQ7i1gfOrXoqekEl42u4mF8Bgu14T5j0189CGcUhJHw2RwCMvON+qbvQ9ecJAA==" syntaxTests :: ATransport -> Spec syntaxTests (ATransport t) = do @@ -519,13 +498,10 @@ syntaxTests (ATransport t) = do noParamsSyntaxTest "OFF" noParamsSyntaxTest "DEL" describe "SEND" $ do - it "valid syntax 1" $ (sampleSig, "cdab", "12345678", "SEND 5 hello ") >#> ("", "cdab", "12345678", "ERR AUTH") - it "valid syntax 2" $ (sampleSig, "dabc", "12345678", "SEND 11 hello there ") >#> ("", "dabc", "12345678", "ERR AUTH") + it "valid syntax 1" $ (sampleSig, "cdab", "12345678", "SEND hello") >#> ("", "cdab", "12345678", "ERR AUTH") + it "valid syntax 2" $ (sampleSig, "dabc", "12345678", "SEND hello there") >#> ("", "dabc", "12345678", "ERR AUTH") it "no parameters" $ (sampleSig, "abcd", "12345678", "SEND") >#> ("", "abcd", "12345678", "ERR CMD SYNTAX") - it "no queue ID" $ (sampleSig, "bcda", "", "SEND 5 hello ") >#> ("", "bcda", "", "ERR CMD NO_QUEUE") - it "bad message body 1" $ (sampleSig, "cdab", "12345678", "SEND 11 hello ") >#> ("", "cdab", "12345678", "ERR CMD SYNTAX") - it "bad message body 2" $ (sampleSig, "dabc", "12345678", "SEND hello ") >#> ("", "dabc", "12345678", "ERR CMD SYNTAX") - it "bigger body" $ (sampleSig, "abcd", "12345678", "SEND 4 hello ") >#> ("", "abcd", "12345678", "ERR CMD SYNTAX") + it "no queue ID" $ (sampleSig, "bcda", "", "SEND hello") >#> ("", "bcda", "", "ERR CMD NO_QUEUE") describe "PING" $ do it "valid syntax" $ ("", "abcd", "", "PING") >#> ("", "abcd", "", "PONG") describe "broker response not allowed" $ do