summaryrefslogtreecommitdiff
path: root/Tox.hs
diff options
context:
space:
mode:
Diffstat (limited to 'Tox.hs')
-rw-r--r--Tox.hs263
1 files changed, 263 insertions, 0 deletions
diff --git a/Tox.hs b/Tox.hs
new file mode 100644
index 00000000..4fd54f04
--- /dev/null
+++ b/Tox.hs
@@ -0,0 +1,263 @@
1{-# LANGUAGE DeriveDataTypeable #-}
2{-# LANGUAGE DeriveFoldable #-}
3{-# LANGUAGE DeriveFunctor #-}
4{-# LANGUAGE DeriveGeneric #-}
5{-# LANGUAGE DeriveTraversable #-}
6{-# LANGUAGE FlexibleInstances #-}
7{-# LANGUAGE GeneralizedNewtypeDeriving #-}
8{-# LANGUAGE PatternSynonyms #-}
9module Tox where
10
11import Control.Arrow
12import qualified Crypto.Cipher.Salsa as Salsa
13import qualified Crypto.Cipher.XSalsa as XSalsa
14import Crypto.ECC.Class
15import qualified Crypto.Error as Cryptonite
16import Crypto.Error.Types
17import qualified Crypto.MAC.Poly1305 as Poly1305
18import Crypto.PubKey.Curve25519
19import Crypto.PubKey.ECC.Types
20import Data.Bool
21import Data.ByteArray as BA
22import Data.ByteString (ByteString)
23import Data.ByteString as B
24import qualified Data.ByteString.Base16 as Base16
25import qualified Data.ByteString.Char8 as C8
26import Data.ByteString.Lazy (toStrict)
27import Data.Data
28import Data.IP
29import qualified Data.Serialize as S
30import Data.Typeable
31import Data.Word
32import GHC.Generics (Generic)
33import Network.Address (Address, fromSockAddr, sockAddrPort,
34 toSockAddr, withPort)
35import Network.QueryResponse
36import Network.Socket
37import Data.Monoid
38
39newtype NodeId = NodeId ByteString
40 deriving (Eq,Ord,Show,ByteArrayAccess)
41
42instance S.Serialize NodeId where
43 get = NodeId <$> S.getBytes 32
44 put (NodeId bs) = S.putByteString bs
45
46data NodeInfo = NodeInfo
47 { nodeId :: NodeId
48 , nodeIP :: IP
49 , nodePort :: PortNumber
50 }
51
52nodeAddr :: NodeInfo -> SockAddr
53nodeAddr (NodeInfo _ ip port) = toSockAddr ip `withPort` port
54
55nodeInfo :: NodeId -> SockAddr -> Either String NodeInfo
56nodeInfo nid saddr
57 | Just ip <- fromSockAddr saddr
58 , Just port <- sockAddrPort saddr = Right $ NodeInfo nid ip port
59 | otherwise = Left "Address family not supported."
60
61type TransactionId = Nonce8
62-- TODO
63-- data TransactionId = TransactionId
64-- { transactionKey :: Nonce8 -- ^ Used to lookup pending query.
65-- , cryptoNonce :: Nonce24 -- ^ Used during encryption and decryption layer.
66-- }
67--
68-- Ensure that cryptoNonce is ignored by 'TableMethods'
69
70newtype Method = MessageType Word8
71 deriving (Eq, Ord, S.Serialize)
72
73pattern PingType = MessageType 0
74pattern PongType = MessageType 1
75pattern GetNodesType = MessageType 2
76pattern SendNodesType = MessageType 4
77
78instance Show Method where
79 showsPrec d PingType = mappend "PingType"
80 showsPrec d PongType = mappend "PongType"
81 showsPrec d GetNodesType = mappend "GetNodesType"
82 showsPrec d SendNodesType = mappend "SendNodesType"
83 showsPrec d (MessageType x) = mappend "MessageType " . showsPrec (d+1) x
84
85-- XXX: Possibly Word64 would be a better implementation.
86newtype Nonce8 = Nonce8 ByteString
87 deriving (Eq, Ord, ByteArrayAccess)
88
89instance Show Nonce8 where
90 showsPrec d nonce = quoted (mappend $ bin2hex nonce)
91
92newtype Nonce24 = Nonce24 ByteString
93 deriving (Eq, Ord, ByteArrayAccess)
94
95instance Show Nonce24 where
96 showsPrec d nonce = quoted (mappend $ bin2hex nonce)
97
98instance S.Serialize Nonce24 where
99 get = Nonce24 <$> S.getBytes 24
100 put (Nonce24 bs) = S.putByteString bs
101
102quoted :: ShowS -> ShowS
103quoted shows s = '"':shows ('"':s)
104
105bin2hex :: ByteArrayAccess bs => bs -> String
106bin2hex = C8.unpack . Base16.encode . convert
107
108
109data Message a = Message
110 { msgType :: Method
111 , msgOrigin :: NodeId
112 , msgNonce :: Nonce24
113 , msgPayload :: a
114 }
115 deriving (Eq, Show, Generic, Functor, Foldable, Traversable)
116
117data Ciphered = Ciphered { cipheredMAC :: Poly1305.Auth
118 , cipheredBytes :: ByteString }
119 deriving Eq
120
121getMessage :: S.Get (Message Ciphered)
122getMessage = do
123 typ <- S.get
124 nid <- S.get
125 tid <- S.get
126 mac <- Poly1305.Auth . convert <$> S.getBytes 16
127 cnt <- S.remaining
128 bs <- S.getBytes cnt
129 return Message { msgType = typ
130 , msgOrigin = nid
131 , msgNonce = tid
132 , msgPayload = Ciphered mac bs }
133
134putMessage :: Message Ciphered -> S.Put
135putMessage (Message {..}) = do
136 S.put msgType
137 S.put msgOrigin
138 S.put msgNonce
139 let Ciphered (Poly1305.Auth mac) bs = msgPayload
140 S.putByteString (convert mac)
141 S.putByteString bs
142
143-- TODO: Cache symmetric keys.
144data SecretsCache = SecretsCache
145newEmptyCache = return SecretsCache
146
147id2key :: NodeId -> PublicKey
148id2key recipient = case publicKey recipient of
149 CryptoPassed key -> key
150 -- This should never happen because a NodeId is 32 bytes.
151 CryptoFailed e -> error ("Unexpected pattern fail: "++show e)
152
153zeros32 :: Bytes
154zeros32 = BA.replicate 32 0
155
156zeros24 :: Bytes
157zeros24 = BA.take 24 zeros32
158
159hsalsa20 k n = a <> b
160 where
161 Salsa.State st = XSalsa.initialize 20 k n
162 (_, as) = BA.splitAt 4 st
163 (a, xs) = BA.splitAt 16 as
164 (_, bs) = BA.splitAt 24 xs
165 (b, _ ) = BA.splitAt 16 bs
166
167
168computeSharedSecret :: SecretKey -> NodeId -> Nonce24 -> (Poly1305.State, XSalsa.State)
169computeSharedSecret sk recipient nonce = (hash, crypt)
170 where
171 -- diffie helman
172 shared = ecdh (Proxy :: Proxy Curve_X25519) sk (id2key recipient)
173 -- shared secret XSalsa key
174 k = hsalsa20 shared zeros24
175 -- cipher state
176 st0 = XSalsa.initialize 20 k nonce
177 -- Poly1305 key
178 (rs, crypt) = XSalsa.combine st0 zeros32
179 -- Since rs is 32 bytes, this pattern should never fail...
180 Cryptonite.CryptoPassed hash = Poly1305.initialize rs
181
182
183encryptMessage :: SecretKey -> SecretsCache -> NodeId -> Message ByteString -> Message Ciphered
184encryptMessage sk _ recipient plaintext
185 = withSecret encipherAndHash sk recipient (msgNonce plaintext) <$> plaintext
186
187decryptMessage :: SecretKey -> SecretsCache -> Message Ciphered -> Either String (Message ByteString)
188decryptMessage sk _ ciphertext
189 = mapM (withSecret decipherAndAuth sk (msgOrigin ciphertext) (msgNonce ciphertext)) ciphertext
190
191withSecret f sk recipient nonce x = f hash crypt x
192 where
193 (hash, crypt) = computeSharedSecret sk recipient nonce
194
195
196encipherAndHash :: Poly1305.State -> XSalsa.State -> ByteString -> Ciphered
197encipherAndHash hash crypt m = Ciphered a c
198 where
199 c = fst . XSalsa.combine crypt $ m
200 a = Poly1305.finalize . Poly1305.update hash $ c
201
202decipherAndAuth :: Poly1305.State -> XSalsa.State -> Ciphered -> Either String ByteString
203decipherAndAuth hash crypt (Ciphered mac c)
204 | (a == mac) = Right m
205 | otherwise = Left "decipherAndAuth: auth fail"
206 where
207 m = fst . XSalsa.combine crypt $ c
208 a = Poly1305.finalize . Poly1305.update hash $ c
209
210
211parsePacket :: SecretKey -> SecretsCache -> ByteString -> SockAddr -> Either String (Message ByteString, NodeInfo)
212parsePacket sk cache bs addr = do ciphered <- S.runGet getMessage bs
213 msg <- decryptMessage sk cache ciphered
214 ni <- nodeInfo (msgOrigin msg) addr
215 return (msg, ni)
216
217encodePacket :: SecretKey -> SecretsCache -> Message ByteString -> NodeInfo -> (ByteString, SockAddr)
218encodePacket sk cache msg ni = ( S.runPut . putMessage $ encryptMessage sk cache (nodeId ni) msg
219 , nodeAddr ni )
220
221newClient addr = do
222 udp <- udpTransport addr
223 secret <- generateSecretKey
224 cache <- newEmptyCache
225 let net = layerTransport (parsePacket secret cache) (encodePacket secret cache) udp
226 return net
227
228last8 :: ByteString -> Nonce8
229last8 bs
230 | let len = B.length bs
231 , (len >= 8) = Nonce8 $ B.drop (len - 8) bs
232 | otherwise = Nonce8 $ B.replicate 8 0
233
234classify :: Message ByteString -> MessageClass String Method TransactionId
235classify (Message { msgType = typ, msgPayload = bs }) = cls (last8 bs)
236 where
237 cls = case typ of
238 PingType -> IsQuery PingType
239 GetNodesType -> IsQuery GetNodesType
240 PongType -> IsResponse
241 SendNodesType -> IsResponse
242
243encodePayload typ _ (Nonce8 tid) self dest b
244 = Message { msgType = typ
245 , msgOrigin = nodeId self
246 , msgNonce = error "encodePayload"
247 , msgPayload = S.encode b <> tid
248 }
249
250decodePayload :: S.Serialize a => Message ByteString -> Either String a
251decodePayload msg = S.decode $ msgPayload msg
252
253handler typ f = Just $ MethodHandler decodePayload (encodePayload typ) f
254
255handlers :: Method -> Maybe (MethodHandler String TransactionId NodeInfo (Message ByteString) ())
256handlers PingType = handler PingType pingH
257handlers GetNodesType = error "find_node"
258handlers _ = Nothing
259
260data Ping = Ping
261
262pingH :: NodeInfo -> Ping -> IO Ping
263pingH = error "pingH"