core: use ChaChaDRG as the source of randomness (#3551)

* core: use ChaChaDRG as the source of randomness

* do not use entropy directly

* dont use RNG from agent

* simplexmq

* update iOS
This commit is contained in:
Evgeny Poberezkin authored and GitHub committed 2023-12-21 00:42:40 +00:00
1 parent 4a4d470859
commit 7bcda7e54b
19 files changed
+120 -94

No files matched your search

@@ -18,6 +18,7 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
}() }()
private static let ivTagBytes: Int = 28 private static let ivTagBytes: Int = 28
private static let enableEncryption: Bool = true private static let enableEncryption: Bool = true
private var chat_ctrl = getChatCtrl()
struct Call { struct Call {
var connection: RTCPeerConnection var connection: RTCPeerConnection
@@ -308,7 +309,7 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg
memcpy(pointer, (unencrypted as NSData).bytes, unencrypted.count) memcpy(pointer, (unencrypted as NSData).bytes, unencrypted.count)
let isKeyFrame = unencrypted[0] & 1 == 0 let isKeyFrame = unencrypted[0] & 1 == 0
let clearTextBytesSize = mediaType.rawValue == 0 ? 1 : isKeyFrame ? 10 : 3 let clearTextBytesSize = mediaType.rawValue == 0 ? 1 : isKeyFrame ? 10 : 3
logCrypto("encrypt", chat_encrypt_media(&key, pointer.advanced(by: clearTextBytesSize), Int32(unencrypted.count + WebRTCClient.ivTagBytes - clearTextBytesSize))) logCrypto("encrypt", chat_encrypt_media(chat_ctrl, &key, pointer.advanced(by: clearTextBytesSize), Int32(unencrypted.count + WebRTCClient.ivTagBytes - clearTextBytesSize)))
return Data(bytes: pointer, count: unencrypted.count + WebRTCClient.ivTagBytes) return Data(bytes: pointer, count: unencrypted.count + WebRTCClient.ivTagBytes)
} else { } else {
return nil return nil
+2 -2
View File
@@ -17,7 +17,7 @@ public func writeCryptoFile(path: String, data: Data) throws -> CryptoFileArgs {
let ptr: UnsafeMutableRawPointer = malloc(data.count) let ptr: UnsafeMutableRawPointer = malloc(data.count)
memcpy(ptr, (data as NSData).bytes, data.count) memcpy(ptr, (data as NSData).bytes, data.count)
var cPath = path.cString(using: .utf8)! var cPath = path.cString(using: .utf8)!
let cjson = chat_write_file(&cPath, ptr, Int32(data.count))! let cjson = chat_write_file(getChatCtrl(), &cPath, ptr, Int32(data.count))!
let d = fromCString(cjson).data(using: .utf8)! let d = fromCString(cjson).data(using: .utf8)!
switch try jsonDecoder.decode(WriteFileResult.self, from: d) { switch try jsonDecoder.decode(WriteFileResult.self, from: d) {
case let .result(cfArgs): return cfArgs case let .result(cfArgs): return cfArgs
@@ -50,7 +50,7 @@ public func readCryptoFile(path: String, cryptoArgs: CryptoFileArgs) throws -> D
public func encryptCryptoFile(fromPath: String, toPath: String) throws -> CryptoFileArgs { public func encryptCryptoFile(fromPath: String, toPath: String) throws -> CryptoFileArgs {
var cFromPath = fromPath.cString(using: .utf8)! var cFromPath = fromPath.cString(using: .utf8)!
var cToPath = toPath.cString(using: .utf8)! var cToPath = toPath.cString(using: .utf8)!
let cjson = chat_encrypt_file(&cFromPath, &cToPath)! let cjson = chat_encrypt_file(getChatCtrl(), &cFromPath, &cToPath)!
let d = fromCString(cjson).data(using: .utf8)! let d = fromCString(cjson).data(using: .utf8)!
switch try jsonDecoder.decode(WriteFileResult.self, from: d) { switch try jsonDecoder.decode(WriteFileResult.self, from: d) {
case let .result(cfArgs): return cfArgs case let .result(cfArgs): return cfArgs
+3 -3
View File
@@ -25,11 +25,11 @@ extern char *chat_parse_markdown(char *str);
extern char *chat_parse_server(char *str); extern char *chat_parse_server(char *str);
extern char *chat_password_hash(char *pwd, char *salt); extern char *chat_password_hash(char *pwd, char *salt);
extern char *chat_valid_name(char *name); extern char *chat_valid_name(char *name);
extern char *chat_encrypt_media(char *key, char *frame, int len); extern char *chat_encrypt_media(chat_ctrl ctl, char *key, char *frame, int len);
extern char *chat_decrypt_media(char *key, char *frame, int len); extern char *chat_decrypt_media(char *key, char *frame, int len);
// chat_write_file returns null-terminated string with JSON of WriteFileResult // chat_write_file returns null-terminated string with JSON of WriteFileResult
extern char *chat_write_file(char *path, char *data, int len); extern char *chat_write_file(chat_ctrl ctl, char *path, char *data, int len);
// chat_read_file returns a buffer with: // chat_read_file returns a buffer with:
// result status (1 byte), then if // result status (1 byte), then if
@@ -38,7 +38,7 @@ extern char *chat_write_file(char *path, char *data, int len);
extern char *chat_read_file(char *path, char *key, char *nonce); extern char *chat_read_file(char *path, char *key, char *nonce);
// chat_encrypt_file returns null-terminated string with JSON of WriteFileResult // chat_encrypt_file returns null-terminated string with JSON of WriteFileResult
extern char *chat_encrypt_file(char *fromPath, char *toPath); extern char *chat_encrypt_file(chat_ctrl ctl, char *fromPath, char *toPath);
// chat_decrypt_file returns null-terminated string with the error message // chat_decrypt_file returns null-terminated string with the error message
extern char *chat_decrypt_file(char *fromPath, char *key, char *nonce, char *toPath); extern char *chat_decrypt_file(char *fromPath, char *key, char *nonce, char *toPath);
+1 -1
View File
@@ -14,7 +14,7 @@ constraints: zip +disable-bzip2 +disable-zstd
source-repository-package source-repository-package
type: git type: git
location: https://github.com/simplex-chat/simplexmq.git location: https://github.com/simplex-chat/simplexmq.git
tag: 8c250ebe19f56dd7d53572d984e8016cb0e4d658 tag: 13a60d1d3944aa175311563e661161e759b92563
source-repository-package source-repository-package
type: git type: git
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"https://github.com/simplex-chat/simplexmq.git"."8c250ebe19f56dd7d53572d984e8016cb0e4d658" = "080rw86yncf1h3zr5a8y65cndihq6f3ji43vxrdhr2mrb75vmw8m"; "https://github.com/simplex-chat/simplexmq.git"."13a60d1d3944aa175311563e661161e759b92563" = "08mvqrbjfnq7c6mhkj4hhy4cxn0cj21n49lqzh67ani71g2g1xwa";
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";
"https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl";
+18 -17
View File
@@ -22,7 +22,6 @@ import Control.Monad
import Control.Monad.Except import Control.Monad.Except
import Control.Monad.IO.Unlift import Control.Monad.IO.Unlift
import Control.Monad.Reader import Control.Monad.Reader
import Crypto.Random (drgNew)
import qualified Data.Aeson as J import qualified Data.Aeson as J
import Data.Attoparsec.ByteString.Char8 (Parser) import Data.Attoparsec.ByteString.Char8 (Parser)
import qualified Data.Attoparsec.ByteString.Char8 as A import qualified Data.Attoparsec.ByteString.Char8 as A
@@ -208,7 +207,7 @@ newChatController ChatDatabase {chatStore, agentStore} user cfg@ChatConfig {agen
servers <- agentServers config servers <- agentServers config
smpAgent <- getSMPAgentClient aCfg {tbqSize} servers agentStore smpAgent <- getSMPAgentClient aCfg {tbqSize} servers agentStore
agentAsync <- newTVarIO Nothing agentAsync <- newTVarIO Nothing
idsDrg <- newTVarIO =<< liftIO drgNew random <- liftIO C.newRandom
inputQ <- newTBQueueIO tbqSize inputQ <- newTBQueueIO tbqSize
outputQ <- newTBQueueIO tbqSize outputQ <- newTBQueueIO tbqSize
connNetworkStatuses <- atomically TM.empty connNetworkStatuses <- atomically TM.empty
@@ -243,7 +242,7 @@ newChatController ChatDatabase {chatStore, agentStore} user cfg@ChatConfig {agen
agentAsync, agentAsync,
chatStore, chatStore,
chatStoreChanged, chatStoreChanged,
idsDrg, random,
inputQ, inputQ,
outputQ, outputQ,
connNetworkStatuses, connNetworkStatuses,
@@ -1077,8 +1076,9 @@ processChatCommand = \case
then do then do
calls <- asks currentCalls calls <- asks currentCalls
withChatLock "sendCallInvitation" $ do withChatLock "sendCallInvitation" $ do
callId <- CallId <$> drgRandomBytes 16 g <- asks random
dhKeyPair <- if encryptedCall callType then Just <$> liftIO C.generateKeyPair' else pure Nothing callId <- atomically $ CallId <$> C.randomBytes 16 g
dhKeyPair <- atomically $ if encryptedCall callType then Just <$> C.generateKeyPair g else pure Nothing
let invitation = CallInvitation {callType, callDhPubKey = fst <$> dhKeyPair} let invitation = CallInvitation {callType, callDhPubKey = fst <$> dhKeyPair}
callState = CallInvitationSent {localCallType = callType, localDhPrivKey = snd <$> dhKeyPair} callState = CallInvitationSent {localCallType = callType, localDhPrivKey = snd <$> dhKeyPair}
(msg, _) <- sendDirectContactMessage ct (XCallInv callId invitation) (msg, _) <- sendDirectContactMessage ct (XCallInv callId invitation)
@@ -1600,7 +1600,7 @@ processChatCommand = \case
processChatCommand $ APIChatItemReaction chatRef chatItemId add reaction processChatCommand $ APIChatItemReaction chatRef chatItemId add reaction
APINewGroup userId incognito gProfile@GroupProfile {displayName} -> withUserId userId $ \user -> do APINewGroup userId incognito gProfile@GroupProfile {displayName} -> withUserId userId $ \user -> do
checkValidName displayName checkValidName displayName
gVar <- asks idsDrg gVar <- asks random
-- [incognito] generate incognito profile for group membership -- [incognito] generate incognito profile for group membership
incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing
groupInfo <- withStore $ \db -> createNewGroup db gVar user gProfile incognitoProfile groupInfo <- withStore $ \db -> createNewGroup db gVar user gProfile incognitoProfile
@@ -1621,7 +1621,7 @@ processChatCommand = \case
let sendInvitation = sendGrpInvitation user contact gInfo let sendInvitation = sendGrpInvitation user contact gInfo
case contactMember contact members of case contactMember contact members of
Nothing -> do Nothing -> do
gVar <- asks idsDrg gVar <- asks random
subMode <- chatReadVar subscriptionMode subMode <- chatReadVar subscriptionMode
(agentConnId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation Nothing subMode (agentConnId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation Nothing subMode
member <- withStore $ \db -> createNewContactMember db gVar user gInfo contact memRole agentConnId cReq subMode member <- withStore $ \db -> createNewContactMember db gVar user gInfo contact memRole agentConnId cReq subMode
@@ -1884,7 +1884,7 @@ processChatCommand = \case
SetFileToReceive fileId encrypted_ -> withUser $ \_ -> do SetFileToReceive fileId encrypted_ -> withUser $ \_ -> do
withChatLock "setFileToReceive" . procCmd $ do withChatLock "setFileToReceive" . procCmd $ do
encrypt <- (`fromMaybe` encrypted_) <$> chatReadVar encryptLocalFiles encrypt <- (`fromMaybe` encrypted_) <$> chatReadVar encryptLocalFiles
cfArgs <- if encrypt then Just <$> liftIO CF.randomArgs else pure Nothing cfArgs <- if encrypt then Just <$> (atomically . CF.randomArgs =<< asks random) else pure Nothing
withStore' $ \db -> setRcvFileToReceive db fileId cfArgs withStore' $ \db -> setRcvFileToReceive db fileId cfArgs
ok_ ok_
CancelFile fileId -> withUser $ \user@User {userId} -> CancelFile fileId -> withUser $ \user@User {userId} ->
@@ -2030,7 +2030,7 @@ processChatCommand = \case
-- in View.hs `r'` should be defined as `id` in this case -- in View.hs `r'` should be defined as `id` in this case
-- procCmd :: m ChatResponse -> m ChatResponse -- procCmd :: m ChatResponse -> m ChatResponse
-- procCmd action = do -- procCmd action = do
-- ChatController {chatLock = l, smpAgent = a, outputQ = q, idsDrg = gVar} <- ask -- ChatController {chatLock = l, smpAgent = a, outputQ = q, random = gVar} <- ask
-- corrId <- liftIO $ SMP.CorrId <$> randomBytes gVar 8 -- corrId <- liftIO $ SMP.CorrId <$> randomBytes gVar 8
-- void . forkIO $ -- void . forkIO $
-- withAgentLock a . withLock l name $ -- withAgentLock a . withLock l name $
@@ -2296,7 +2296,7 @@ processChatCommand = \case
then pure Nothing then pure Nothing
else Just . addUTCTime (realToFrac ttl) <$> liftIO getCurrentTime else Just . addUTCTime (realToFrac ttl) <$> liftIO getCurrentTime
drgRandomBytes :: Int -> m ByteString drgRandomBytes :: Int -> m ByteString
drgRandomBytes n = asks idsDrg >>= liftIO . (`randomBytes` n) drgRandomBytes n = asks random >>= atomically . C.randomBytes n
privateGetUser :: UserId -> m User privateGetUser :: UserId -> m User
privateGetUser userId = privateGetUser userId =
tryChatError (withStore (`getUser` userId)) >>= \case tryChatError (withStore (`getUser` userId)) >>= \case
@@ -2571,7 +2571,7 @@ toFSFilePath f =
setFileToEncrypt :: ChatMonad m => RcvFileTransfer -> m RcvFileTransfer setFileToEncrypt :: ChatMonad m => RcvFileTransfer -> m RcvFileTransfer
setFileToEncrypt ft@RcvFileTransfer {fileId} = do setFileToEncrypt ft@RcvFileTransfer {fileId} = do
cfArgs <- liftIO CF.randomArgs cfArgs <- atomically . CF.randomArgs =<< asks random
withStore' $ \db -> setFileCryptoArgs db fileId cfArgs withStore' $ \db -> setFileCryptoArgs db fileId cfArgs
pure (ft :: RcvFileTransfer) {cryptoArgs = Just cfArgs} pure (ft :: RcvFileTransfer) {cryptoArgs = Just cfArgs}
@@ -2726,7 +2726,7 @@ acceptGroupJoinRequestAsync
ucr@UserContactRequest {agentInvitationId = AgentInvId invId} ucr@UserContactRequest {agentInvitationId = AgentInvId invId}
gLinkMemRole gLinkMemRole
incognitoProfile = do incognitoProfile = do
gVar <- asks idsDrg gVar <- asks random
(groupMemberId, memberId) <- withStore $ \db -> createAcceptedMember db gVar user gInfo ucr gLinkMemRole (groupMemberId, memberId) <- withStore $ \db -> createAcceptedMember db gVar user gInfo ucr gLinkMemRole
let Profile {displayName} = profileToSendOnAccept user incognitoProfile let Profile {displayName} = profileToSendOnAccept user incognitoProfile
GroupMember {memberRole = userRole, memberId = userMemberId} = membership GroupMember {memberRole = userRole, memberId = userMemberId} = membership
@@ -3407,7 +3407,7 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
groupInfo <- withStore $ \db -> getGroupInfo db user groupId groupInfo <- withStore $ \db -> getGroupInfo db user groupId
subMode <- chatReadVar subscriptionMode subMode <- chatReadVar subscriptionMode
groupConnIds <- createAgentConnectionAsync user CFCreateConnGrpInv True SCMInvitation subMode groupConnIds <- createAgentConnectionAsync user CFCreateConnGrpInv True SCMInvitation subMode
gVar <- asks idsDrg gVar <- asks random
withStore $ \db -> createNewContactMemberAsync db gVar user groupInfo ct gLinkMemRole groupConnIds (fromJVersionRange peerChatVRange) subMode withStore $ \db -> createNewContactMemberAsync db gVar user groupInfo ct gLinkMemRole groupConnIds (fromJVersionRange peerChatVRange) subMode
Just (gInfo, m@GroupMember {activeConn}) -> Just (gInfo, m@GroupMember {activeConn}) ->
when (maybe False ((== ConnReady) . connStatus) activeConn) $ do when (maybe False ((== ConnReady) . connStatus) activeConn) $ do
@@ -4049,7 +4049,7 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
probeMatchingContactsAndMembers :: Contact -> IncognitoEnabled -> Bool -> m () probeMatchingContactsAndMembers :: Contact -> IncognitoEnabled -> Bool -> m ()
probeMatchingContactsAndMembers ct connectedIncognito doProbeContacts = do probeMatchingContactsAndMembers ct connectedIncognito doProbeContacts = do
gVar <- asks idsDrg gVar <- asks random
contactMerge <- readTVarIO =<< asks contactMergeEnabled contactMerge <- readTVarIO =<< asks contactMergeEnabled
if contactMerge && not connectedIncognito if contactMerge && not connectedIncognito
then do then do
@@ -4073,7 +4073,7 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
probeMatchingMemberContact :: GroupMember -> IncognitoEnabled -> m () probeMatchingMemberContact :: GroupMember -> IncognitoEnabled -> m ()
probeMatchingMemberContact GroupMember {activeConn = Nothing} _ = pure () probeMatchingMemberContact GroupMember {activeConn = Nothing} _ = pure ()
probeMatchingMemberContact m@GroupMember {groupId, activeConn = Just conn} connectedIncognito = do probeMatchingMemberContact m@GroupMember {groupId, activeConn = Just conn} connectedIncognito = do
gVar <- asks idsDrg gVar <- asks random
contactMerge <- readTVarIO =<< asks contactMergeEnabled contactMerge <- readTVarIO =<< asks contactMergeEnabled
if contactMerge && not connectedIncognito if contactMerge && not connectedIncognito
then do then do
@@ -4774,7 +4774,8 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
checkIntegrityCreateItem (CDDirectRcv ct) msgMeta checkIntegrityCreateItem (CDDirectRcv ct) msgMeta
if featureAllowed SCFCalls forContact ct if featureAllowed SCFCalls forContact ct
then do then do
dhKeyPair <- if encryptedCall callType then Just <$> liftIO C.generateKeyPair' else pure Nothing g <- asks random
dhKeyPair <- atomically $ if encryptedCall callType then Just <$> C.generateKeyPair g else pure Nothing
ci <- saveCallItem CISCallPending ci <- saveCallItem CISCallPending
let sharedKey = C.Key . C.dhBytes' <$> (C.dh' <$> callDhPubKey <*> (snd <$> dhKeyPair)) let sharedKey = C.Key . C.dhBytes' <$> (C.dh' <$> callDhPubKey <*> (snd <$> dhKeyPair))
callState = CallInvitationReceived {peerCallType = callType, localDhPubKey = fst <$> dhKeyPair, sharedKey} callState = CallInvitationReceived {peerCallType = callType, localDhPubKey = fst <$> dhKeyPair, sharedKey}
@@ -5517,7 +5518,7 @@ sendDirectMessage conn chatMsgEvent connOrGroupId = do
createSndMessage :: (MsgEncodingI e, ChatMonad m) => ChatMsgEvent e -> ConnOrGroupId -> m SndMessage createSndMessage :: (MsgEncodingI e, ChatMonad m) => ChatMsgEvent e -> ConnOrGroupId -> m SndMessage
createSndMessage chatMsgEvent connOrGroupId = do createSndMessage chatMsgEvent connOrGroupId = do
gVar <- asks idsDrg gVar <- asks random
ChatConfig {chatVRange} <- asks config ChatConfig {chatVRange} <- asks config
withStore $ \db -> createNewSndMessage db gVar connOrGroupId $ \sharedMsgId -> withStore $ \db -> createNewSndMessage db gVar connOrGroupId $ \sharedMsgId ->
let msgBody = strEncode ChatMessage {chatVRange, msgId = Just sharedMsgId, chatMsgEvent} let msgBody = strEncode ChatMessage {chatVRange, msgId = Just sharedMsgId, chatMsgEvent}
+1 -1
View File
@@ -180,7 +180,7 @@ data ChatController = ChatController
agentAsync :: TVar (Maybe (Async (), Maybe (Async ()))), agentAsync :: TVar (Maybe (Async (), Maybe (Async ()))),
chatStore :: SQLiteStore, chatStore :: SQLiteStore,
chatStoreChanged :: TVar Bool, -- if True, chat should be fully restarted chatStoreChanged :: TVar Bool, -- if True, chat should be fully restarted
idsDrg :: TVar ChaChaDRG, random :: TVar ChaChaDRG,
inputQ :: TBQueue String, inputQ :: TBQueue String,
outputQ :: TBQueue (Maybe CorrId, Maybe RemoteHostId, ChatResponse), outputQ :: TBQueue (Maybe CorrId, Maybe RemoteHostId, ChatResponse),
connNetworkStatuses :: TMap AgentConnId NetworkStatus, connNetworkStatuses :: TMap AgentConnId NetworkStatus,
+3 -3
View File
@@ -94,15 +94,15 @@ foreign export ccall "chat_password_hash" cChatPasswordHash :: CString -> CStrin
foreign export ccall "chat_valid_name" cChatValidName :: CString -> IO CString foreign export ccall "chat_valid_name" cChatValidName :: CString -> IO CString
foreign export ccall "chat_encrypt_media" cChatEncryptMedia :: CString -> Ptr Word8 -> CInt -> IO CString foreign export ccall "chat_encrypt_media" cChatEncryptMedia :: StablePtr ChatController -> CString -> Ptr Word8 -> CInt -> IO CString
foreign export ccall "chat_decrypt_media" cChatDecryptMedia :: CString -> Ptr Word8 -> CInt -> IO CString foreign export ccall "chat_decrypt_media" cChatDecryptMedia :: CString -> Ptr Word8 -> CInt -> IO CString
foreign export ccall "chat_write_file" cChatWriteFile :: CString -> Ptr Word8 -> CInt -> IO CJSONString foreign export ccall "chat_write_file" cChatWriteFile :: StablePtr ChatController -> CString -> Ptr Word8 -> CInt -> IO CJSONString
foreign export ccall "chat_read_file" cChatReadFile :: CString -> CString -> CString -> IO (Ptr Word8) foreign export ccall "chat_read_file" cChatReadFile :: CString -> CString -> CString -> IO (Ptr Word8)
foreign export ccall "chat_encrypt_file" cChatEncryptFile :: CString -> CString -> IO CJSONString foreign export ccall "chat_encrypt_file" cChatEncryptFile :: StablePtr ChatController -> CString -> CString -> IO CJSONString
foreign export ccall "chat_decrypt_file" cChatDecryptFile :: CString -> CString -> CString -> CString -> IO CString foreign export ccall "chat_decrypt_file" cChatDecryptFile :: CString -> CString -> CString -> CString -> IO CString
+18 -13
View File
@@ -1,5 +1,6 @@
{-# LANGUAGE BangPatterns #-} {-# LANGUAGE BangPatterns #-}
{-# LANGUAGE LambdaCase #-} {-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-} {-# LANGUAGE TupleSections #-}
@@ -31,7 +32,9 @@ import Data.Word (Word32, Word8)
import Foreign.C import Foreign.C
import Foreign.Marshal.Alloc (mallocBytes) import Foreign.Marshal.Alloc (mallocBytes)
import Foreign.Ptr import Foreign.Ptr
import Foreign.StablePtr
import Foreign.Storable (poke, pokeByteOff) import Foreign.Storable (poke, pokeByteOff)
import Simplex.Chat.Controller (ChatController (..))
import Simplex.Chat.Mobile.Shared import Simplex.Chat.Mobile.Shared
import Simplex.Chat.Util (chunkSize, encryptFile) import Simplex.Chat.Util (chunkSize, encryptFile)
import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..), CryptoFileHandle, FTCryptoError (..)) import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..), CryptoFileHandle, FTCryptoError (..))
@@ -39,7 +42,7 @@ import qualified Simplex.Messaging.Crypto.File as CF
import Simplex.Messaging.Encoding.String import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Parsers (dropPrefix, sumTypeJSON) import Simplex.Messaging.Parsers (dropPrefix, sumTypeJSON)
import Simplex.Messaging.Util (catchAll) import Simplex.Messaging.Util (catchAll)
import UnliftIO (Handle, IOMode (..), withFile) import UnliftIO (Handle, IOMode (..), atomically, withFile)
data WriteFileResult data WriteFileResult
= WFResult {cryptoArgs :: CryptoFileArgs} = WFResult {cryptoArgs :: CryptoFileArgs}
@@ -47,16 +50,17 @@ data WriteFileResult
$(JQ.deriveToJSON (sumTypeJSON $ dropPrefix "WF") ''WriteFileResult) $(JQ.deriveToJSON (sumTypeJSON $ dropPrefix "WF") ''WriteFileResult)
cChatWriteFile :: CString -> Ptr Word8 -> CInt -> IO CJSONString cChatWriteFile :: StablePtr ChatController -> CString -> Ptr Word8 -> CInt -> IO CJSONString
cChatWriteFile cPath ptr len = do cChatWriteFile cc cPath ptr len = do
c <- deRefStablePtr cc
path <- peekCString cPath path <- peekCString cPath
s <- getByteString ptr len s <- getByteString ptr len
r <- chatWriteFile path s r <- chatWriteFile c path s
newCStringFromLazyBS $ J.encode r newCStringFromLazyBS $ J.encode r
chatWriteFile :: FilePath -> ByteString -> IO WriteFileResult chatWriteFile :: ChatController -> FilePath -> ByteString -> IO WriteFileResult
chatWriteFile path s = do chatWriteFile ChatController {random} path s = do
cfArgs <- CF.randomArgs cfArgs <- atomically $ CF.randomArgs random
let file = CryptoFile path $ Just cfArgs let file = CryptoFile path $ Just cfArgs
either WFError (\_ -> WFResult cfArgs) either WFError (\_ -> WFResult cfArgs)
<$> runCatchExceptT (withExceptT show $ CF.writeFile file $ LB.fromStrict s) <$> runCatchExceptT (withExceptT show $ CF.writeFile file $ LB.fromStrict s)
@@ -87,19 +91,20 @@ chatReadFile path keyStr nonceStr = runCatchExceptT $ do
let file = CryptoFile path $ Just $ CFArgs key nonce let file = CryptoFile path $ Just $ CFArgs key nonce
withExceptT show $ CF.readFile file withExceptT show $ CF.readFile file
cChatEncryptFile :: CString -> CString -> IO CJSONString cChatEncryptFile :: StablePtr ChatController -> CString -> CString -> IO CJSONString
cChatEncryptFile cFromPath cToPath = do cChatEncryptFile cc cFromPath cToPath = do
c <- deRefStablePtr cc
fromPath <- peekCString cFromPath fromPath <- peekCString cFromPath
toPath <- peekCString cToPath toPath <- peekCString cToPath
r <- chatEncryptFile fromPath toPath r <- chatEncryptFile c fromPath toPath
newCAString . LB'.unpack $ J.encode r newCAString . LB'.unpack $ J.encode r
chatEncryptFile :: FilePath -> FilePath -> IO WriteFileResult chatEncryptFile :: ChatController -> FilePath -> FilePath -> IO WriteFileResult
chatEncryptFile fromPath toPath = chatEncryptFile ChatController {random} fromPath toPath =
either WFError WFResult <$> runCatchExceptT encrypt either WFError WFResult <$> runCatchExceptT encrypt
where where
encrypt = do encrypt = do
cfArgs <- liftIO CF.randomArgs cfArgs <- atomically $ CF.randomArgs random
encryptFile fromPath toPath cfArgs encryptFile fromPath toPath cfArgs
pure cfArgs pure cfArgs
+10 -5
View File
@@ -1,4 +1,5 @@
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NamedFieldPuns #-}
module Simplex.Chat.Mobile.WebRTC module Simplex.Chat.Mobile.WebRTC
( cChatEncryptMedia, ( cChatEncryptMedia,
@@ -21,11 +22,14 @@ import Data.Either (fromLeft)
import Data.Word (Word8) import Data.Word (Word8)
import Foreign.C (CInt, CString, newCAString) import Foreign.C (CInt, CString, newCAString)
import Foreign.Ptr (Ptr) import Foreign.Ptr (Ptr)
import Foreign.StablePtr
import Simplex.Chat.Controller (ChatController (..))
import Simplex.Chat.Mobile.Shared import Simplex.Chat.Mobile.Shared
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
import UnliftIO (atomically)
cChatEncryptMedia :: CString -> Ptr Word8 -> CInt -> IO CString cChatEncryptMedia :: StablePtr ChatController -> CString -> Ptr Word8 -> CInt -> IO CString
cChatEncryptMedia = cTransformMedia chatEncryptMedia cChatEncryptMedia = cTransformMedia . chatEncryptMedia
cChatDecryptMedia :: CString -> Ptr Word8 -> CInt -> IO CString cChatDecryptMedia :: CString -> Ptr Word8 -> CInt -> IO CString
cChatDecryptMedia = cTransformMedia chatDecryptMedia cChatDecryptMedia = cTransformMedia chatDecryptMedia
@@ -39,11 +43,12 @@ cTransformMedia f cKey cFrame cFrameLen = do
putFrame s = when (B.length s <= fromIntegral cFrameLen) $ putByteString cFrame s putFrame s = when (B.length s <= fromIntegral cFrameLen) $ putByteString cFrame s
{-# INLINE cTransformMedia #-} {-# INLINE cTransformMedia #-}
chatEncryptMedia :: ByteString -> ByteString -> ExceptT String IO ByteString chatEncryptMedia :: StablePtr ChatController -> ByteString -> ByteString -> ExceptT String IO ByteString
chatEncryptMedia keyStr frame = do chatEncryptMedia cc keyStr frame = do
ChatController {random} <- liftIO $ deRefStablePtr cc
len <- checkFrameLen frame len <- checkFrameLen frame
key <- decodeKey keyStr key <- decodeKey keyStr
iv <- liftIO C.randomGCMIV iv <- atomically $ C.randomGCMIV random
(tag, frame') <- withExceptT show $ C.encryptAESNoPad key iv $ B.take len frame (tag, frame') <- withExceptT show $ C.encryptAESNoPad key iv $ B.take len frame
pure $ frame' <> BA.convert (C.unAuthTag tag) <> C.unGCMIV iv pure $ frame' <> BA.convert (C.unAuthTag tag) <> C.unGCMIV iv
+2 -2
View File
@@ -142,7 +142,7 @@ startRemoteHost rh_ rcAddrPrefs_ port_ = do
Just (rhId, multicast) -> do Just (rhId, multicast) -> do
rh@RemoteHost {hostPairing} <- withStore $ \db -> getRemoteHost db rhId rh@RemoteHost {hostPairing} <- withStore $ \db -> getRemoteHost db rhId
pure (RHId rhId, multicast, Just $ remoteHostInfo rh $ Just RHSStarting, hostPairing) -- get from the database, start multicast if requested pure (RHId rhId, multicast, Just $ remoteHostInfo rh $ Just RHSStarting, hostPairing) -- get from the database, start multicast if requested
Nothing -> (RHNew,False,Nothing,) <$> rcNewHostPairing Nothing -> withAgent $ \a -> (RHNew,False,Nothing,) <$> rcNewHostPairing a
sseq <- startRemoteHostSession rhKey sseq <- startRemoteHostSession rhKey
ctrlAppInfo <- mkCtrlAppInfo ctrlAppInfo <- mkCtrlAppInfo
(localAddrs, invitation, rchClient, vars) <- handleConnectError rhKey sseq . withAgent $ \a -> rcConnectHost a pairing (J.toJSON ctrlAppInfo) multicast rcAddrPrefs_ port_ (localAddrs, invitation, rchClient, vars) <- handleConnectError rhKey sseq . withAgent $ \a -> rcConnectHost a pairing (J.toJSON ctrlAppInfo) multicast rcAddrPrefs_ port_
@@ -352,7 +352,7 @@ storeRemoteFile rhId encrypted_ localPath = do
tmpDir <- getChatTempDirectory tmpDir <- getChatTempDirectory
createDirectoryIfMissing True tmpDir createDirectoryIfMissing True tmpDir
tmpFile <- tmpDir `uniqueCombine` takeFileName localPath tmpFile <- tmpDir `uniqueCombine` takeFileName localPath
cfArgs <- liftIO CF.randomArgs cfArgs <- atomically . CF.randomArgs =<< asks random
liftError (ChatError . CEFileWrite tmpFile) $ encryptFile localPath tmpFile cfArgs liftError (ChatError . CEFileWrite tmpFile) $ encryptFile localPath tmpFile cfArgs
pure $ CryptoFile tmpFile $ Just cfArgs pure $ CryptoFile tmpFile $ Just cfArgs
+2 -2
View File
@@ -78,7 +78,7 @@ $(deriveJSON (taggedObjectJSON $ dropPrefix "RR") ''RemoteResponse)
mkRemoteHostClient :: ChatMonad m => HTTP2Client -> HostSessKeys -> SessionCode -> FilePath -> HostAppInfo -> m RemoteHostClient mkRemoteHostClient :: ChatMonad m => HTTP2Client -> HostSessKeys -> SessionCode -> FilePath -> HostAppInfo -> m RemoteHostClient
mkRemoteHostClient httpClient sessionKeys sessionCode storePath HostAppInfo {encoding, deviceName, encryptFiles} = do mkRemoteHostClient httpClient sessionKeys sessionCode storePath HostAppInfo {encoding, deviceName, encryptFiles} = do
drg <- asks $ agentDRG . smpAgent drg <- asks random
counter <- newTVarIO 1 counter <- newTVarIO 1
let HostSessKeys {hybridKey, idPrivKey, sessPrivKey} = sessionKeys let HostSessKeys {hybridKey, idPrivKey, sessPrivKey} = sessionKeys
signatures = RSSign {idPrivKey, sessPrivKey} signatures = RSSign {idPrivKey, sessPrivKey}
@@ -95,7 +95,7 @@ mkRemoteHostClient httpClient sessionKeys sessionCode storePath HostAppInfo {enc
mkCtrlRemoteCrypto :: ChatMonad m => CtrlSessKeys -> SessionCode -> m RemoteCrypto mkCtrlRemoteCrypto :: ChatMonad m => CtrlSessKeys -> SessionCode -> m RemoteCrypto
mkCtrlRemoteCrypto CtrlSessKeys {hybridKey, idPubKey, sessPubKey} sessionCode = do mkCtrlRemoteCrypto CtrlSessKeys {hybridKey, idPubKey, sessPubKey} sessionCode = do
drg <- asks $ agentDRG . smpAgent drg <- asks random
counter <- newTVarIO 1 counter <- newTVarIO 1
let signatures = RSVerify {idPubKey, sessPubKey} let signatures = RSVerify {idPubKey, sessPubKey}
pure RemoteCrypto {drg, counter, sessionCode, hybridKey, signatures} pure RemoteCrypto {drg, counter, sessionCode, hybridKey, signatures}
+1 -1
View File
@@ -24,7 +24,7 @@ type EncryptedFile = ((Handle, Word32), C.CbNonce, LC.SbState)
prepareEncryptedFile :: RemoteCrypto -> (Handle, Word32) -> ExceptT RemoteProtocolError IO EncryptedFile prepareEncryptedFile :: RemoteCrypto -> (Handle, Word32) -> ExceptT RemoteProtocolError IO EncryptedFile
prepareEncryptedFile RemoteCrypto {drg, hybridKey} f = do prepareEncryptedFile RemoteCrypto {drg, hybridKey} f = do
nonce <- atomically $ C.pseudoRandomCbNonce drg nonce <- atomically $ C.randomCbNonce drg
sbState <- liftEitherWith (const $ PRERemoteControl RCEEncrypt) $ LC.kcbInit hybridKey nonce sbState <- liftEitherWith (const $ PRERemoteControl RCEEncrypt) $ LC.kcbInit hybridKey nonce
pure (f, nonce, sbState) pure (f, nonce, sbState)
+3 -5
View File
@@ -15,7 +15,7 @@ import qualified Control.Exception as E
import Control.Monad import Control.Monad
import Control.Monad.Except import Control.Monad.Except
import Control.Monad.IO.Class import Control.Monad.IO.Class
import Crypto.Random (ChaChaDRG, randomBytesGenerate) import Crypto.Random (ChaChaDRG)
import qualified Data.Aeson.TH as J import qualified Data.Aeson.TH as J
import qualified Data.ByteString.Base64 as B64 import qualified Data.ByteString.Base64 as B64
import Data.ByteString.Char8 (ByteString) import Data.ByteString.Char8 (ByteString)
@@ -35,6 +35,7 @@ import Simplex.Chat.Types.Preferences
import Simplex.Messaging.Agent.Protocol (AgentMsgId, ConnId, UserId) import Simplex.Messaging.Agent.Protocol (AgentMsgId, ConnId, UserId)
import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow) import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow)
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB
import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Parsers (dropPrefix, sumTypeJSON) import Simplex.Messaging.Parsers (dropPrefix, sumTypeJSON)
import Simplex.Messaging.Protocol (SubscriptionMode (..)) import Simplex.Messaging.Protocol (SubscriptionMode (..))
import Simplex.Messaging.Util (allFinally) import Simplex.Messaging.Util (allFinally)
@@ -389,7 +390,4 @@ createWithRandomBytes size gVar create = tryCreate 3
| otherwise -> throwError . SEInternalError $ show e | otherwise -> throwError . SEInternalError $ show e
encodedRandomBytes :: TVar ChaChaDRG -> Int -> IO ByteString encodedRandomBytes :: TVar ChaChaDRG -> Int -> IO ByteString
encodedRandomBytes gVar = fmap B64.encode . randomBytes gVar encodedRandomBytes gVar n = atomically $ B64.encode <$> C.randomBytes n gVar
randomBytes :: TVar ChaChaDRG -> Int -> IO ByteString
randomBytes gVar = atomically . stateTVar gVar . randomBytesGenerate
+1 -1
View File
@@ -1094,7 +1094,7 @@ testXFTPFileTransferEncrypted =
let srcPath = "./tests/tmp/alice/test.pdf" let srcPath = "./tests/tmp/alice/test.pdf"
createDirectoryIfMissing True "./tests/tmp/alice/" createDirectoryIfMissing True "./tests/tmp/alice/"
createDirectoryIfMissing True "./tests/tmp/bob/" createDirectoryIfMissing True "./tests/tmp/bob/"
WFResult cfArgs <- chatWriteFile srcPath src WFResult cfArgs <- chatWriteFile (chatController alice) srcPath src
let fileJSON = LB.unpack $ J.encode $ CryptoFile srcPath $ Just cfArgs let fileJSON = LB.unpack $ J.encode $ CryptoFile srcPath $ Just cfArgs
withXFTPServer $ do withXFTPServer $ do
connectUsers alice bob connectUsers alice bob
+26 -14
View File
@@ -8,8 +8,8 @@
module MobileTests where module MobileTests where
import ChatTests.Utils import ChatTests.Utils
import Control.Concurrent.STM
import Control.Monad.Except import Control.Monad.Except
import Crypto.Random (getRandomBytes)
import Data.Aeson (FromJSON) import Data.Aeson (FromJSON)
import qualified Data.Aeson as J import qualified Data.Aeson as J
import qualified Data.Aeson.TH as JQ import qualified Data.Aeson.TH as JQ
@@ -22,8 +22,10 @@ import Data.Word (Word8, Word32)
import Foreign.C import Foreign.C
import Foreign.Marshal.Alloc (mallocBytes) import Foreign.Marshal.Alloc (mallocBytes)
import Foreign.Ptr import Foreign.Ptr
import Foreign.StablePtr
import Foreign.Storable (peek) import Foreign.Storable (peek)
import GHC.IO.Encoding (setLocaleEncoding, setFileSystemEncoding, setForeignEncoding) import GHC.IO.Encoding (setLocaleEncoding, setFileSystemEncoding, setForeignEncoding)
import Simplex.Chat.Controller (ChatController (..))
import Simplex.Chat.Mobile import Simplex.Chat.Mobile
import Simplex.Chat.Mobile.File import Simplex.Chat.Mobile.File
import Simplex.Chat.Mobile.Shared import Simplex.Chat.Mobile.Shared
@@ -226,25 +228,29 @@ testChatApi tmp = do
chatParseMarkdown "*hello*" `shouldBe` parsedMarkdown chatParseMarkdown "*hello*" `shouldBe` parsedMarkdown
testMediaApi :: HasCallStack => FilePath -> IO () testMediaApi :: HasCallStack => FilePath -> IO ()
testMediaApi _ = do testMediaApi tmp = do
key :: ByteString <- getRandomBytes 32 Right c@ChatController {random = g} <- chatMigrateInit (tmp </> "1") "" "yesUp"
frame <- getRandomBytes 100 cc <- newStablePtr c
key <- atomically $ C.randomBytes 32 g
frame <- atomically $ C.randomBytes 100 g
let keyStr = strEncode key let keyStr = strEncode key
reserved = B.replicate (C.authTagSize + C.gcmIVSize) 0 reserved = B.replicate (C.authTagSize + C.gcmIVSize) 0
frame' = frame <> reserved frame' = frame <> reserved
Right encrypted <- runExceptT $ chatEncryptMedia keyStr frame' Right encrypted <- runExceptT $ chatEncryptMedia cc keyStr frame'
encrypted `shouldNotBe` frame' encrypted `shouldNotBe` frame'
B.length encrypted `shouldBe` B.length frame' B.length encrypted `shouldBe` B.length frame'
runExceptT (chatDecryptMedia keyStr encrypted) `shouldReturn` Right frame' runExceptT (chatDecryptMedia keyStr encrypted) `shouldReturn` Right frame'
testMediaCApi :: HasCallStack => FilePath -> IO () testMediaCApi :: HasCallStack => FilePath -> IO ()
testMediaCApi _ = do testMediaCApi tmp = do
key :: ByteString <- getRandomBytes 32 Right c@ChatController {random = g} <- chatMigrateInit (tmp </> "1") "" "yesUp"
frame <- getRandomBytes 100 cc <- newStablePtr c
key <- atomically $ C.randomBytes 32 g
frame <- atomically $ C.randomBytes 100 g
let keyStr = strEncode key let keyStr = strEncode key
reserved = B.replicate (C.authTagSize + C.gcmIVSize) 0 reserved = B.replicate (C.authTagSize + C.gcmIVSize) 0
frame' = frame <> reserved frame' = frame <> reserved
encrypted <- test cChatEncryptMedia keyStr frame' encrypted <- test (cChatEncryptMedia cc) keyStr frame'
encrypted `shouldNotBe` frame' encrypted `shouldNotBe` frame'
test cChatDecryptMedia keyStr encrypted `shouldReturn` frame' test cChatDecryptMedia keyStr encrypted `shouldReturn` frame'
where where
@@ -266,6 +272,7 @@ instance FromJSON ReadFileResult where
testFileCApi :: FilePath -> FilePath -> IO () testFileCApi :: FilePath -> FilePath -> IO ()
testFileCApi fileName tmp = do testFileCApi fileName tmp = do
cc <- mkCCPtr tmp
src <- B.readFile "./tests/fixtures/test.pdf" src <- B.readFile "./tests/fixtures/test.pdf"
let path = tmp </> (fileName <> ".pdf") let path = tmp </> (fileName <> ".pdf")
cPath <- newCString path cPath <- newCString path
@@ -273,7 +280,7 @@ testFileCApi fileName tmp = do
cLen = fromIntegral len cLen = fromIntegral len
ptr <- mallocBytes $ B.length src ptr <- mallocBytes $ B.length src
putByteString ptr src putByteString ptr src
r <- peekCAString =<< cChatWriteFile cPath ptr cLen r <- peekCAString =<< cChatWriteFile cc cPath ptr cLen
Just (WFResult cfArgs@(CFArgs key nonce)) <- jDecode r Just (WFResult cfArgs@(CFArgs key nonce)) <- jDecode r
let encryptedFile = CryptoFile path $ Just cfArgs let encryptedFile = CryptoFile path $ Just cfArgs
CF.getFileContentsSize encryptedFile `shouldReturn` fromIntegral (B.length src) CF.getFileContentsSize encryptedFile `shouldReturn` fromIntegral (B.length src)
@@ -292,7 +299,7 @@ testMissingFileCApi :: FilePath -> IO ()
testMissingFileCApi tmp = do testMissingFileCApi tmp = do
let path = tmp </> "missing_file" let path = tmp </> "missing_file"
cPath <- newCString path cPath <- newCString path
CFArgs key nonce <- CF.randomArgs CFArgs key nonce <- atomically . CF.randomArgs =<< C.newRandom
cKey <- encodedCString key cKey <- encodedCString key
cNonce <- encodedCString nonce cNonce <- encodedCString nonce
ptr <- cChatReadFile cPath cKey cNonce ptr <- cChatReadFile cPath cKey cNonce
@@ -302,13 +309,14 @@ testMissingFileCApi tmp = do
testFileEncryptionCApi :: FilePath -> FilePath -> IO () testFileEncryptionCApi :: FilePath -> FilePath -> IO ()
testFileEncryptionCApi fileName tmp = do testFileEncryptionCApi fileName tmp = do
cc <- mkCCPtr tmp
let fromPath = tmp </> (fileName <> ".source.pdf") let fromPath = tmp </> (fileName <> ".source.pdf")
copyFile "./tests/fixtures/test.pdf" fromPath copyFile "./tests/fixtures/test.pdf" fromPath
src <- B.readFile fromPath src <- B.readFile fromPath
cFromPath <- newCString fromPath cFromPath <- newCString fromPath
let toPath = tmp </> (fileName <> ".encrypted.pdf") let toPath = tmp </> (fileName <> ".encrypted.pdf")
cToPath <- newCString toPath cToPath <- newCString toPath
r <- peekCAString =<< cChatEncryptFile cFromPath cToPath r <- peekCAString =<< cChatEncryptFile cc cFromPath cToPath
Just (WFResult cfArgs@(CFArgs key nonce)) <- jDecode r Just (WFResult cfArgs@(CFArgs key nonce)) <- jDecode r
CF.getFileContentsSize (CryptoFile toPath $ Just cfArgs) `shouldReturn` fromIntegral (B.length src) CF.getFileContentsSize (CryptoFile toPath $ Just cfArgs) `shouldReturn` fromIntegral (B.length src)
cKey <- encodedCString key cKey <- encodedCString key
@@ -320,14 +328,15 @@ testFileEncryptionCApi fileName tmp = do
testMissingFileEncryptionCApi :: FilePath -> IO () testMissingFileEncryptionCApi :: FilePath -> IO ()
testMissingFileEncryptionCApi tmp = do testMissingFileEncryptionCApi tmp = do
cc <- mkCCPtr tmp
let fromPath = tmp </> "missing_file.source.pdf" let fromPath = tmp </> "missing_file.source.pdf"
toPath = tmp </> "missing_file.encrypted.pdf" toPath = tmp </> "missing_file.encrypted.pdf"
cFromPath <- newCString fromPath cFromPath <- newCString fromPath
cToPath <- newCString toPath cToPath <- newCString toPath
r <- peekCAString =<< cChatEncryptFile cFromPath cToPath r <- peekCAString =<< cChatEncryptFile cc cFromPath cToPath
Just (WFError err) <- jDecode r Just (WFError err) <- jDecode r
err `shouldContain` fromPath err `shouldContain` fromPath
CFArgs key nonce <- CF.randomArgs CFArgs key nonce <- atomically . CF.randomArgs =<< C.newRandom
cKey <- encodedCString key cKey <- encodedCString key
cNonce <- encodedCString nonce cNonce <- encodedCString nonce
let toPath' = tmp </> "missing_file.decrypted.pdf" let toPath' = tmp </> "missing_file.decrypted.pdf"
@@ -335,6 +344,9 @@ testMissingFileEncryptionCApi tmp = do
err' <- peekCAString =<< cChatDecryptFile cToPath cKey cNonce cToPath' err' <- peekCAString =<< cChatDecryptFile cToPath cKey cNonce cToPath'
err' `shouldContain` toPath err' `shouldContain` toPath
mkCCPtr :: FilePath -> IO (StablePtr ChatController)
mkCCPtr tmp = either (error . show) newStablePtr =<< chatMigrateInit (tmp </> "1") "" "yesUp"
testValidNameCApi :: FilePath -> IO () testValidNameCApi :: FilePath -> IO ()
testValidNameCApi _ = do testValidNameCApi _ = do
let goodName = "Джон Доу 👍" let goodName = "Джон Доу 👍"
-10
View File
@@ -11,18 +11,14 @@ import Control.Logger.Simple
import qualified Data.Aeson as J import qualified Data.Aeson as J
import qualified Data.ByteString as B import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy.Char8 as LB import qualified Data.ByteString.Lazy.Char8 as LB
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.Map.Strict as M import qualified Data.Map.Strict as M
import qualified Network.TLS as TLS
import Simplex.Chat.Archive (archiveFilesFolder) import Simplex.Chat.Archive (archiveFilesFolder)
import Simplex.Chat.Controller (ChatConfig (..), XFTPFileConfig (..), versionNumber) import Simplex.Chat.Controller (ChatConfig (..), XFTPFileConfig (..), versionNumber)
import qualified Simplex.Chat.Controller as Controller import qualified Simplex.Chat.Controller as Controller
import Simplex.Chat.Mobile.File import Simplex.Chat.Mobile.File
import Simplex.Chat.Remote.Types import Simplex.Chat.Remote.Types
import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Crypto.File (CryptoFileArgs (..)) import Simplex.Messaging.Crypto.File (CryptoFileArgs (..))
import Simplex.Messaging.Encoding.String (strEncode) import Simplex.Messaging.Encoding.String (strEncode)
import Simplex.Messaging.Transport.Credentials (genCredentials, tlsCredentials)
import Simplex.Messaging.Util import Simplex.Messaging.Util
import System.FilePath ((</>)) import System.FilePath ((</>))
import Test.Hspec import Test.Hspec
@@ -571,12 +567,6 @@ contactBob desktop bob = do
(desktop <## "bob (Bob): contact is connected") (desktop <## "bob (Bob): contact is connected")
(bob <## "alice (Alice): contact is connected") (bob <## "alice (Alice): contact is connected")
genTestCredentials :: IO (C.KeyHash, TLS.Credentials)
genTestCredentials = do
caCreds <- liftIO $ genCredentials Nothing (0, 24) "CA"
sessionCreds <- liftIO $ genCredentials (Just caCreds) (0, 24) "Session"
pure . tlsCredentials $ sessionCreds :| [caCreds]
stopDesktop :: HasCallStack => TestCC -> TestCC -> IO () stopDesktop :: HasCallStack => TestCC -> TestCC -> IO ()
stopDesktop mobile desktop = do stopDesktop mobile desktop = do
logWarn "stopping via desktop" logWarn "stopping via desktop"
+4 -3
View File
@@ -26,7 +26,7 @@ main = do
describe "JSON Tests" jsonTests describe "JSON Tests" jsonTests
describe "SimpleX chat view" viewTests describe "SimpleX chat view" viewTests
describe "SimpleX chat protocol" protocolTests describe "SimpleX chat protocol" protocolTests
describe "WebRTC encryption" webRTCTests around tmpBracket $ describe "WebRTC encryption" webRTCTests
describe "Valid names" validNameTests describe "Valid names" validNameTests
around testBracket $ do around testBracket $ do
describe "Mobile API Tests" mobileTests describe "Mobile API Tests" mobileTests
@@ -35,10 +35,11 @@ main = do
xdescribe'' "SimpleX Directory service bot" directoryServiceTests xdescribe'' "SimpleX Directory service bot" directoryServiceTests
describe "Remote session" remoteTests describe "Remote session" remoteTests
where where
testBracket test = do testBracket test = withSmpServer $ tmpBracket test
tmpBracket test = do
t <- getSystemTime t <- getSystemTime
let ts = show (systemSeconds t) <> show (systemNanoseconds t) let ts = show (systemSeconds t) <> show (systemNanoseconds t)
withSmpServer $ withTmpFiles $ withTempDirectory "tests/tmp" ts test withTmpFiles $ withTempDirectory "tests/tmp" ts test
logCfg :: LogConfig logCfg :: LogConfig
logCfg = LogConfig {lc_file = Nothing, lc_stderr = True} logCfg = LogConfig {lc_file = Nothing, lc_stderr = True}
+22 -9
View File
@@ -1,36 +1,49 @@
{-# LANGUAGE OverloadedStrings #-}
module WebRTCTests where module WebRTCTests where
import Control.Monad.Except import Control.Monad.Except
import Crypto.Random (getRandomBytes) import Crypto.Random (getRandomBytes)
import qualified Data.ByteString.Base64.URL as U import qualified Data.ByteString.Base64.URL as U
import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Char8 as B
import Foreign.StablePtr
import Simplex.Chat.Mobile
import Simplex.Chat.Mobile.WebRTC import Simplex.Chat.Mobile.WebRTC
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
import System.FilePath ((</>))
import Test.Hspec import Test.Hspec
webRTCTests :: Spec webRTCTests :: SpecWith FilePath
webRTCTests = describe "WebRTC crypto" $ do webRTCTests = describe "WebRTC crypto" $ do
it "encrypts and decrypts media" $ do it "encrypts and decrypts media" $ \tmp -> do
Right c <- chatMigrateInit (tmp </> "1") "" "yesUp"
cc <- newStablePtr c
key <- U.encode <$> getRandomBytes 32 key <- U.encode <$> getRandomBytes 32
frame <- getRandomBytes 1000 frame <- getRandomBytes 1000
Right frame' <- runExceptT $ chatEncryptMedia key $ frame <> B.replicate reservedSize '\NUL' Right frame' <- runExceptT $ chatEncryptMedia cc key $ frame <> B.replicate reservedSize '\NUL'
B.length frame' `shouldBe` B.length frame + reservedSize B.length frame' `shouldBe` B.length frame + reservedSize
Right frame'' <- runExceptT $ chatDecryptMedia key frame' Right frame'' <- runExceptT $ chatDecryptMedia key frame'
frame'' `shouldBe` frame <> B.replicate reservedSize '\NUL' frame'' `shouldBe` frame <> B.replicate reservedSize '\NUL'
it "should fail on invalid frame size" $ do it "should fail on invalid frame size" $ \tmp -> do
Right c <- chatMigrateInit (tmp </> "1") "" "yesUp"
cc <- newStablePtr c
key <- U.encode <$> getRandomBytes 32 key <- U.encode <$> getRandomBytes 32
frame <- getRandomBytes 10 frame <- getRandomBytes 10
runExceptT (chatEncryptMedia key frame) `shouldReturn` Left "frame has no [reserved space for] IV and/or auth tag" runExceptT (chatEncryptMedia cc key frame) `shouldReturn` Left "frame has no [reserved space for] IV and/or auth tag"
runExceptT (chatDecryptMedia key frame) `shouldReturn` Left "frame has no [reserved space for] IV and/or auth tag" runExceptT (chatDecryptMedia key frame) `shouldReturn` Left "frame has no [reserved space for] IV and/or auth tag"
it "should fail on invalid key" $ do it "should fail on invalid key" $ \tmp -> do
Right c <- chatMigrateInit (tmp </> "1") "" "yesUp"
cc <- newStablePtr c
let key = B.replicate 32 '#' let key = B.replicate 32 '#'
frame <- (<> B.replicate reservedSize '\NUL') <$> getRandomBytes 100 frame <- (<> B.replicate reservedSize '\NUL') <$> getRandomBytes 100
runExceptT (chatEncryptMedia key frame) `shouldReturn` Left "invalid key: invalid character at offset: 0" runExceptT (chatEncryptMedia cc key frame) `shouldReturn` Left "invalid key: invalid character at offset: 0"
runExceptT (chatDecryptMedia key frame) `shouldReturn` Left "invalid key: invalid character at offset: 0" runExceptT (chatDecryptMedia key frame) `shouldReturn` Left "invalid key: invalid character at offset: 0"
it "should fail on invalid auth tag" $ do it "should fail on invalid auth tag" $ \tmp -> do
Right c <- chatMigrateInit (tmp </> "1") "" "yesUp"
cc <- newStablePtr c
key <- U.encode <$> getRandomBytes 32 key <- U.encode <$> getRandomBytes 32
frame <- getRandomBytes 1000 frame <- getRandomBytes 1000
Right frame' <- runExceptT $ chatEncryptMedia key $ frame <> B.replicate reservedSize '\NUL' Right frame' <- runExceptT $ chatEncryptMedia cc key $ frame <> B.replicate reservedSize '\NUL'
Right frame'' <- runExceptT $ chatDecryptMedia key frame' Right frame'' <- runExceptT $ chatDecryptMedia key frame'
frame'' `shouldBe` frame <> B.replicate reservedSize '\NUL' frame'' `shouldBe` frame <> B.replicate reservedSize '\NUL'
let (encFrame, rest) = B.splitAt (B.length frame' - reservedSize) frame let (encFrame, rest) = B.splitAt (B.length frame' - reservedSize) frame