diff options
author | joe <joe@jerkface.net> | 2017-08-27 18:59:23 -0400 |
---|---|---|
committer | joe <joe@jerkface.net> | 2017-08-27 18:59:23 -0400 |
commit | 5472805a6a8fb3c3d64cbeff5bda1d78a898c602 (patch) | |
tree | 015eed92ebbbe72d3ed07b1959dc5d15719d91b2 | |
parent | 396b6daf475b1769a214e0d3ee8b476ff415d2f9 (diff) |
reworking... ToxTransport and related modules.
-rw-r--r-- | ToxAddress.hs | 383 | ||||
-rw-r--r-- | ToxCrypto.hs | 203 | ||||
-rw-r--r-- | ToxData.hs | 23 | ||||
-rw-r--r-- | ToxPacket.hs | 76 | ||||
-rw-r--r-- | ToxTransport.hs | 199 | ||||
-rwxr-xr-x | c | 2 |
6 files changed, 885 insertions, 1 deletions
diff --git a/ToxAddress.hs b/ToxAddress.hs new file mode 100644 index 00000000..08c9031b --- /dev/null +++ b/ToxAddress.hs | |||
@@ -0,0 +1,383 @@ | |||
1 | {-# LANGUAGE BangPatterns #-} | ||
2 | {-# LANGUAGE CPP #-} | ||
3 | {-# LANGUAGE DataKinds #-} | ||
4 | {-# LANGUAGE DeriveDataTypeable #-} | ||
5 | {-# LANGUAGE DeriveFunctor #-} | ||
6 | {-# LANGUAGE DeriveTraversable #-} | ||
7 | {-# LANGUAGE ExistentialQuantification #-} | ||
8 | {-# LANGUAGE FlexibleInstances #-} | ||
9 | {-# LANGUAGE GADTs #-} | ||
10 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | ||
11 | {-# LANGUAGE KindSignatures #-} | ||
12 | {-# LANGUAGE PatternSynonyms #-} | ||
13 | {-# LANGUAGE ScopedTypeVariables #-} | ||
14 | {-# LANGUAGE TupleSections #-} | ||
15 | {-# LANGUAGE TypeApplications #-} | ||
16 | module ToxAddress where | ||
17 | |||
18 | import Control.Applicative | ||
19 | import Control.Monad | ||
20 | import qualified Data.Aeson as JSON | ||
21 | ;import Data.Aeson (FromJSON, ToJSON, (.=)) | ||
22 | import Data.Bits | ||
23 | import Data.Bits.ByteString () | ||
24 | import Data.ByteArray as BA (ByteArrayAccess, Bytes) | ||
25 | import qualified Data.ByteArray as BA | ||
26 | import qualified Data.ByteString as B | ||
27 | ;import Data.ByteString (ByteString) | ||
28 | import qualified Data.ByteString.Base16 as Base16 | ||
29 | import qualified Data.ByteString.Char8 as C8 | ||
30 | import Data.Char | ||
31 | import Data.Data | ||
32 | import Data.Hashable | ||
33 | import Data.IP | ||
34 | import Data.Serialize as S | ||
35 | import Data.Word | ||
36 | import Network.Address hiding (nodePort) | ||
37 | import Network.Socket | ||
38 | import qualified Text.ParserCombinators.ReadP as RP | ||
39 | import Text.Read | ||
40 | import GHC.TypeLits | ||
41 | import Crypto.PubKey.Curve25519 | ||
42 | |||
43 | data Address | ||
44 | = DHTNode NodeInfo -- A direct DHT exchange. | ||
45 | | OnionFromOwner NodeInfo (ForwardPath 3) -- Your own created onion path. | ||
46 | | OnionToOwner NodeInfo (ReturnPath 3) -- An onion path end point. | ||
47 | |||
48 | -- | perform io for hashes that do allocation and ffi. | ||
49 | -- unsafeDupablePerformIO is used when possible as the | ||
50 | -- computation is pure and the output is directly linked | ||
51 | -- to the input. we also do not modify anything after it has | ||
52 | -- been returned to the user. | ||
53 | unsafeDoIO :: IO a -> a | ||
54 | #if __GLASGOW_HASKELL__ > 704 | ||
55 | unsafeDoIO = unsafeDupablePerformIO | ||
56 | #else | ||
57 | unsafeDoIO = unsafePerformIO | ||
58 | #endif | ||
59 | |||
60 | unpackPublicKey :: PublicKey -> [Word64] | ||
61 | unpackPublicKey bs = loop 0 | ||
62 | where loop i | ||
63 | | i == 4 = [] | ||
64 | | otherwise = | ||
65 | let !v = unsafeDoIO $ BA.withByteArray bs (\p -> peekElemOff p i) | ||
66 | in v : loop (i+1) | ||
67 | |||
68 | newtype NodeId = NodeId PublicKey | ||
69 | deriving (Eq,ByteArrayAccess) -- (Eq,Ord,ByteArrayAccess, Bits, Hashable) | ||
70 | |||
71 | instance Ord NodeId where | ||
72 | compare (NodeId a) (NodeId b) = compare (unpackPublicKey a) (unpackPublicKey b) | ||
73 | |||
74 | zeroID :: NodeId | ||
75 | zeroID = NodeId $ throwCryptoError $ publicKey $ B.replicate 32 0 | ||
76 | |||
77 | {- | ||
78 | instance Read NodeId where | ||
79 | readsPrec _ str | ||
80 | | (bs, xs) <- Base16.decode $ C8.pack str | ||
81 | , B.length bs == 32 | ||
82 | = [ (NodeId bs, drop 64 str) ] | ||
83 | | otherwise = [] | ||
84 | -} | ||
85 | |||
86 | instance Show NodeId where | ||
87 | show (NodeId bs) = C8.unpack $ Base16.encode $ BA.convert bs | ||
88 | |||
89 | instance S.Serialize NodeId where | ||
90 | get = NodeId . throwCryptoError . publicKey <$> S.getBytes 32 | ||
91 | put (NodeId bs) = S.putByteString $ BA.convert bs | ||
92 | |||
93 | data NodeInfo = NodeInfo | ||
94 | { nodeId :: NodeId | ||
95 | , nodeIP :: IP | ||
96 | , nodePort :: PortNumber | ||
97 | } | ||
98 | deriving (Eq,Ord) | ||
99 | |||
100 | nodeInfo :: NodeId -> SockAddr -> Either String NodeInfo | ||
101 | nodeInfo nid saddr | ||
102 | | Just ip <- fromSockAddr saddr | ||
103 | , Just port <- sockAddrPort saddr = Right $ NodeInfo nid ip port | ||
104 | | otherwise = Left "Address family not supported." | ||
105 | |||
106 | |||
107 | instance ToJSON NodeInfo where | ||
108 | toJSON (NodeInfo nid (IPv4 ip) port) | ||
109 | = JSON.object [ "public_key" .= show nid | ||
110 | , "ipv4" .= show ip | ||
111 | , "port" .= (fromIntegral port :: Int) | ||
112 | ] | ||
113 | toJSON (NodeInfo nid (IPv6 ip6) port) | ||
114 | | Just ip <- un4map ip6 | ||
115 | = JSON.object [ "public_key" .= show nid | ||
116 | , "ipv4" .= show ip | ||
117 | , "port" .= (fromIntegral port :: Int) | ||
118 | ] | ||
119 | | otherwise | ||
120 | = JSON.object [ "public_key" .= show nid | ||
121 | , "ipv6" .= show ip6 | ||
122 | , "port" .= (fromIntegral port :: Int) | ||
123 | ] | ||
124 | instance FromJSON NodeInfo where | ||
125 | parseJSON (JSON.Object v) = do | ||
126 | nidstr <- v JSON..: "public_key" | ||
127 | ip6str <- v JSON..:? "ipv6" | ||
128 | ip4str <- v JSON..:? "ipv4" | ||
129 | portnum <- v JSON..: "port" | ||
130 | ip <- maybe empty (return . IPv6) (ip6str >>= readMaybe) | ||
131 | <|> maybe empty (return . IPv4) (ip4str >>= readMaybe) | ||
132 | let (bs,_) = Base16.decode (C8.pack nidstr) | ||
133 | guard (B.length bs == 32) | ||
134 | return $ NodeInfo (NodeId $ throwCryptoError . publicKey $ bs) ip (fromIntegral (portnum :: Word16)) | ||
135 | |||
136 | getIP :: Word8 -> S.Get IP | ||
137 | getIP 0x02 = IPv4 <$> S.get | ||
138 | getIP 0x0a = IPv6 <$> S.get | ||
139 | getIP 0x82 = IPv4 <$> S.get -- TODO: TCP | ||
140 | getIP 0x8a = IPv6 <$> S.get -- TODO: TCP | ||
141 | getIP x = fail ("unsupported address family ("++show x++")") | ||
142 | |||
143 | instance S.Serialize NodeInfo where | ||
144 | get = do | ||
145 | addrfam <- S.get :: S.Get Word8 | ||
146 | ip <- getIP addrfam | ||
147 | port <- S.get :: S.Get PortNumber | ||
148 | nid <- S.get | ||
149 | return $ NodeInfo nid ip port | ||
150 | |||
151 | put (NodeInfo nid ip port) = do | ||
152 | case ip of | ||
153 | IPv4 ip4 -> S.put (2 :: Word8) >> S.put ip4 | ||
154 | IPv6 ip6 -> S.put (10 :: Word8) >> S.put ip6 | ||
155 | S.put port | ||
156 | S.put nid | ||
157 | |||
158 | hexdigit :: Char -> Bool | ||
159 | hexdigit c = ('0' <= c && c <= '9') || ( 'a' <= c && c <= 'f') || ( 'A' <= c && c <= 'F') | ||
160 | |||
161 | instance Read NodeInfo where | ||
162 | readsPrec i = RP.readP_to_S $ do | ||
163 | RP.skipSpaces | ||
164 | let n = 64 -- characters in node id. | ||
165 | parseAddr = RP.between (RP.char '(') (RP.char ')') (RP.munch (/=')')) | ||
166 | RP.+++ RP.munch (not . isSpace) | ||
167 | nodeidAt = do hexhash <- sequence $ replicate n (RP.satisfy hexdigit) | ||
168 | RP.char '@' RP.+++ RP.satisfy isSpace | ||
169 | addrstr <- parseAddr | ||
170 | nid <- case Base16.decode $ C8.pack hexhash of | ||
171 | (bs,_) | B.length bs==32 -> return (NodeId $ throwCryptoError . publicKey $ bs) | ||
172 | _ -> fail "Bad node id." | ||
173 | return (nid,addrstr) | ||
174 | (nid,addrstr) <- ( nodeidAt RP.+++ ( (zeroID,) <$> parseAddr) ) | ||
175 | let raddr = do | ||
176 | ip <- RP.between (RP.char '[') (RP.char ']') | ||
177 | (IPv6 <$> RP.readS_to_P (readsPrec i)) | ||
178 | RP.+++ (IPv4 <$> RP.readS_to_P (readsPrec i)) | ||
179 | _ <- RP.char ':' | ||
180 | port <- toEnum <$> RP.readS_to_P (readsPrec i) | ||
181 | return (ip, port) | ||
182 | |||
183 | (ip,port) <- case RP.readP_to_S raddr addrstr of | ||
184 | [] -> fail "Bad address." | ||
185 | ((ip,port),_):_ -> return (ip,port) | ||
186 | return $ NodeInfo nid ip port | ||
187 | |||
188 | -- The Hashable instance depends only on the IP address and port number. | ||
189 | instance Hashable NodeInfo where | ||
190 | hashWithSalt s ni = hashWithSalt s (nodeIP ni , nodePort ni) | ||
191 | {-# INLINE hashWithSalt #-} | ||
192 | |||
193 | |||
194 | instance Show NodeInfo where | ||
195 | showsPrec _ (NodeInfo nid ip port) = | ||
196 | shows nid . ('@' :) . showsip . (':' :) . shows port | ||
197 | where | ||
198 | showsip | ||
199 | | IPv4 ip4 <- ip = shows ip4 | ||
200 | | IPv6 ip6 <- ip , Just ip4 <- un4map ip6 = shows ip4 | ||
201 | | otherwise = ('[' :) . shows ip . (']' :) | ||
202 | |||
203 | |||
204 | |||
205 | |||
206 | {- | ||
207 | type NodeId = PubKey | ||
208 | |||
209 | pattern NodeId bs = PubKey bs | ||
210 | |||
211 | -- TODO: This should probably be represented by Curve25519.PublicKey, but | ||
212 | -- ByteString has more instances... | ||
213 | newtype PubKey = PubKey ByteString | ||
214 | deriving (Eq,Ord,Data, ByteArrayAccess, Bits, Hashable) | ||
215 | |||
216 | instance Serialize PubKey where | ||
217 | get = PubKey <$> getBytes 32 | ||
218 | put (PubKey bs) = putByteString bs | ||
219 | |||
220 | instance Show PubKey where | ||
221 | show (PubKey bs) = C8.unpack $ Base16.encode bs | ||
222 | |||
223 | instance FiniteBits PubKey where | ||
224 | finiteBitSize _ = 256 | ||
225 | |||
226 | instance Read PubKey where | ||
227 | readsPrec _ str | ||
228 | | (bs, xs) <- Base16.decode $ C8.pack str | ||
229 | , B.length bs == 32 | ||
230 | = [ (PubKey bs, drop 64 str) ] | ||
231 | | otherwise = [] | ||
232 | |||
233 | |||
234 | |||
235 | |||
236 | data NodeInfo = NodeInfo | ||
237 | { nodeId :: NodeId | ||
238 | , nodeIP :: IP | ||
239 | , nodePort :: PortNumber | ||
240 | } | ||
241 | deriving (Eq,Ord,Data) | ||
242 | |||
243 | instance Data PortNumber where | ||
244 | dataTypeOf _ = mkNoRepType "PortNumber" | ||
245 | toConstr _ = error "PortNumber.toConstr" | ||
246 | gunfold _ _ = error "PortNumber.gunfold" | ||
247 | |||
248 | instance ToJSON NodeInfo where | ||
249 | toJSON (NodeInfo nid (IPv4 ip) port) | ||
250 | = JSON.object [ "public_key" .= show nid | ||
251 | , "ipv4" .= show ip | ||
252 | , "port" .= (fromIntegral port :: Int) | ||
253 | ] | ||
254 | toJSON (NodeInfo nid (IPv6 ip6) port) | ||
255 | | Just ip <- un4map ip6 | ||
256 | = JSON.object [ "public_key" .= show nid | ||
257 | , "ipv4" .= show ip | ||
258 | , "port" .= (fromIntegral port :: Int) | ||
259 | ] | ||
260 | | otherwise | ||
261 | = JSON.object [ "public_key" .= show nid | ||
262 | , "ipv6" .= show ip6 | ||
263 | , "port" .= (fromIntegral port :: Int) | ||
264 | ] | ||
265 | instance FromJSON NodeInfo where | ||
266 | parseJSON (JSON.Object v) = do | ||
267 | nidstr <- v JSON..: "public_key" | ||
268 | ip6str <- v JSON..:? "ipv6" | ||
269 | ip4str <- v JSON..:? "ipv4" | ||
270 | portnum <- v JSON..: "port" | ||
271 | ip <- maybe empty (return . IPv6) (ip6str >>= readMaybe) | ||
272 | <|> maybe empty (return . IPv4) (ip4str >>= readMaybe) | ||
273 | let (bs,_) = Base16.decode (C8.pack nidstr) | ||
274 | guard (B.length bs == 32) | ||
275 | return $ NodeInfo (NodeId bs) ip (fromIntegral (portnum :: Word16)) | ||
276 | |||
277 | getIP :: Word8 -> S.Get IP | ||
278 | getIP 0x02 = IPv4 <$> S.get | ||
279 | getIP 0x0a = IPv6 <$> S.get | ||
280 | getIP 0x82 = IPv4 <$> S.get -- TODO: TCP | ||
281 | getIP 0x8a = IPv6 <$> S.get -- TODO: TCP | ||
282 | getIP x = fail ("unsupported address family ("++show x++")") | ||
283 | |||
284 | instance S.Serialize NodeInfo where | ||
285 | get = do | ||
286 | addrfam <- S.get :: S.Get Word8 | ||
287 | ip <- getIP addrfam | ||
288 | port <- S.get :: S.Get PortNumber | ||
289 | nid <- S.get | ||
290 | return $ NodeInfo nid ip port | ||
291 | |||
292 | put (NodeInfo nid ip port) = do | ||
293 | case ip of | ||
294 | IPv4 ip4 -> S.put (2 :: Word8) >> S.put ip4 | ||
295 | IPv6 ip6 -> S.put (10 :: Word8) >> S.put ip6 | ||
296 | S.put port | ||
297 | S.put nid | ||
298 | |||
299 | -- node format: | ||
300 | -- [uint8_t family (2 == IPv4, 10 == IPv6, 130 == TCP IPv4, 138 == TCP IPv6)] | ||
301 | -- [ip (in network byte order), length=4 bytes if ipv4, 16 bytes if ipv6] | ||
302 | -- [port (in network byte order), length=2 bytes] | ||
303 | -- [char array (node_id), length=32 bytes] | ||
304 | -- | ||
305 | |||
306 | |||
307 | hexdigit :: Char -> Bool | ||
308 | hexdigit c = ('0' <= c && c <= '9') || ( 'a' <= c && c <= 'f') || ( 'A' <= c && c <= 'F') | ||
309 | |||
310 | instance Read NodeInfo where | ||
311 | readsPrec i = RP.readP_to_S $ do | ||
312 | RP.skipSpaces | ||
313 | let n = 64 -- characters in node id. | ||
314 | parseAddr = RP.between (RP.char '(') (RP.char ')') (RP.munch (/=')')) | ||
315 | RP.+++ RP.munch (not . isSpace) | ||
316 | nodeidAt = do hexhash <- sequence $ replicate n (RP.satisfy hexdigit) | ||
317 | RP.char '@' RP.+++ RP.satisfy isSpace | ||
318 | addrstr <- parseAddr | ||
319 | nid <- case Base16.decode $ C8.pack hexhash of | ||
320 | (bs,_) | B.length bs==32 -> return (PubKey bs) | ||
321 | _ -> fail "Bad node id." | ||
322 | return (nid,addrstr) | ||
323 | (nid,addrstr) <- ( nodeidAt RP.+++ ( (zeroID,) <$> parseAddr) ) | ||
324 | let raddr = do | ||
325 | ip <- RP.between (RP.char '[') (RP.char ']') | ||
326 | (IPv6 <$> RP.readS_to_P (readsPrec i)) | ||
327 | RP.+++ (IPv4 <$> RP.readS_to_P (readsPrec i)) | ||
328 | _ <- RP.char ':' | ||
329 | port <- toEnum <$> RP.readS_to_P (readsPrec i) | ||
330 | return (ip, port) | ||
331 | |||
332 | (ip,port) <- case RP.readP_to_S raddr addrstr of | ||
333 | [] -> fail "Bad address." | ||
334 | ((ip,port),_):_ -> return (ip,port) | ||
335 | return $ NodeInfo nid ip port | ||
336 | |||
337 | |||
338 | -- The Hashable instance depends only on the IP address and port number. | ||
339 | instance Hashable NodeInfo where | ||
340 | hashWithSalt s ni = hashWithSalt s (nodeIP ni , nodePort ni) | ||
341 | {-# INLINE hashWithSalt #-} | ||
342 | |||
343 | |||
344 | instance Show NodeInfo where | ||
345 | showsPrec _ (NodeInfo nid ip port) = | ||
346 | shows nid . ('@' :) . showsip . (':' :) . shows port | ||
347 | where | ||
348 | showsip | ||
349 | | IPv4 ip4 <- ip = shows ip4 | ||
350 | | IPv6 ip6 <- ip , Just ip4 <- un4map ip6 = shows ip4 | ||
351 | | otherwise = ('[' :) . shows ip . (']' :) | ||
352 | |||
353 | nodeAddr :: NodeInfo -> SockAddr | ||
354 | nodeAddr (NodeInfo _ ip port) = setPort port $ toSockAddr ip | ||
355 | |||
356 | nodeInfo :: NodeId -> SockAddr -> Either String NodeInfo | ||
357 | nodeInfo nid saddr | ||
358 | | Just ip <- fromSockAddr saddr | ||
359 | , Just port <- sockAddrPort saddr = Right $ NodeInfo nid ip port | ||
360 | | otherwise = Left "Address family not supported." | ||
361 | |||
362 | zeroID :: NodeId | ||
363 | zeroID = PubKey $ B.replicate 32 0 | ||
364 | |||
365 | -} | ||
366 | |||
367 | newtype ReturnPath (n::Nat) = ReturnPath ByteString | ||
368 | deriving (Eq, Ord,Data) | ||
369 | |||
370 | instance KnownNat n => Serialize (ReturnPath n) where | ||
371 | -- Size: 59 = 1(family) + 16(ip) + 2(port) +16(mac) + 24(nonce) | ||
372 | get = ReturnPath <$> getBytes ( 59 * (fromIntegral $ natVal $ Proxy @n) ) | ||
373 | put (ReturnPath bs) = putByteString bs | ||
374 | |||
375 | newtype ForwardPath (n::Nat) = ForwardPath ByteString | ||
376 | deriving (Eq, Ord,Data) | ||
377 | |||
378 | {- | ||
379 | class KnownNat n => OnionPacket n where | ||
380 | mkOnion :: ReturnPath n -> Packet -> Packet | ||
381 | instance OnionPacket 0 where mkOnion _ = id | ||
382 | instance OnionPacket 3 where mkOnion = OnionResponse3 | ||
383 | -} | ||
diff --git a/ToxCrypto.hs b/ToxCrypto.hs new file mode 100644 index 00000000..98e02e91 --- /dev/null +++ b/ToxCrypto.hs | |||
@@ -0,0 +1,203 @@ | |||
1 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | ||
2 | {-# LANGUAGE ScopedTypeVariables #-} | ||
3 | {-# LANGUAGE KindSignatures #-} | ||
4 | {-# LANGUAGE DeriveDataTypeable #-} | ||
5 | module ToxCrypto | ||
6 | ( PublicKey | ||
7 | , publicKey | ||
8 | , SecretKey | ||
9 | , Encrypted | ||
10 | , Plain | ||
11 | , computeSharedSecret | ||
12 | , encrypt | ||
13 | , decrypt | ||
14 | , Nonce8 | ||
15 | , Nonce24 | ||
16 | , Nonce32 | ||
17 | , getRemainingEncrypted | ||
18 | , putEncrypted | ||
19 | , Auth | ||
20 | ) where | ||
21 | |||
22 | import qualified Crypto.Cipher.Salsa as Salsa | ||
23 | import qualified Crypto.Cipher.XSalsa as XSalsa | ||
24 | import Crypto.ECC.Class | ||
25 | import qualified Crypto.Error as Cryptonite | ||
26 | import qualified Crypto.MAC.Poly1305 as Poly1305 | ||
27 | import Crypto.PubKey.Curve25519 | ||
28 | import qualified Data.ByteArray as BA | ||
29 | ;import Data.ByteArray as BA (ByteArrayAccess, Bytes) | ||
30 | import Data.ByteString as B | ||
31 | import qualified Data.ByteString.Base16 as Base16 | ||
32 | import qualified Data.ByteString.Char8 as C8 | ||
33 | import Data.Data | ||
34 | import Data.Kind | ||
35 | import Data.Ord | ||
36 | import Data.Serialize | ||
37 | import Data.Word | ||
38 | import Foreign.Marshal.Alloc | ||
39 | import Foreign.Ptr | ||
40 | import Foreign.Storable | ||
41 | import System.Endian | ||
42 | import qualified Data.ByteString.Internal | ||
43 | |||
44 | -- | A 16-byte mac and an arbitrary-length encrypted stream. | ||
45 | newtype Encrypted a = Encrypted ByteString | ||
46 | deriving (Eq,Ord,Data) | ||
47 | |||
48 | newtype Auth = Auth Poly1305.Auth deriving (Eq, ByteArrayAccess) | ||
49 | instance Ord Auth where | ||
50 | compare (Auth a) (Auth b) = comparing (BA.convert :: Poly1305.Auth -> Bytes) a b | ||
51 | instance Data Auth where | ||
52 | gfoldl k z x = z x | ||
53 | -- Well, this is a little wonky... XXX | ||
54 | gunfold k z c = k (z (Auth . Poly1305.Auth . (BA.convert :: ByteString -> Bytes))) | ||
55 | toConstr _ = con_Auth | ||
56 | dataTypeOf _ = mkDataType "ToxMessage" [con_Auth] | ||
57 | con_Auth = mkConstr (dataTypeOf (Auth (error "con_Auth"))) "Auth" [] Prefix | ||
58 | instance Serialize Auth where | ||
59 | get = Auth . Poly1305.Auth . BA.convert <$> getBytes 16 | ||
60 | put (Auth (Poly1305.Auth bs)) = putByteString $ BA.convert bs | ||
61 | |||
62 | encryptedAuth :: Encrypted a -> Auth | ||
63 | encryptedAuth (Encrypted bs) | ||
64 | | Right auth <- decode (B.take 16 bs) = auth | ||
65 | | otherwise = error "encryptedAuth: insufficient bytes" | ||
66 | |||
67 | authAndBytes :: Encrypted a -> (Auth, ByteString) | ||
68 | authAndBytes (Encrypted bs) = (auth,bs') | ||
69 | where | ||
70 | (as,bs') = B.splitAt 16 bs | ||
71 | Right auth = decode as | ||
72 | |||
73 | data Size a = ConstSize Int | ||
74 | | VarSize (a -> Int) | ||
75 | |||
76 | class Sized a where size :: Size a | ||
77 | |||
78 | instance Sized a => Serialize (Encrypted a) where | ||
79 | get = case size :: Size a of | ||
80 | VarSize _ -> Encrypted <$> (remaining >>= getBytes) | ||
81 | ConstSize n -> Encrypted <$> getBytes (16 + n) -- 16 extra for Poly1305 mac | ||
82 | put = putEncrypted | ||
83 | |||
84 | getRemainingEncrypted :: Get (Encrypted a) | ||
85 | getRemainingEncrypted = Encrypted <$> (remaining >>= getBytes) | ||
86 | |||
87 | putEncrypted :: Encrypted a -> Put | ||
88 | putEncrypted (Encrypted bs) = putByteString bs | ||
89 | |||
90 | newtype Plain (s:: * -> Constraint) a = Plain ByteString | ||
91 | |||
92 | |||
93 | decodePlain :: Serialize a => Plain Serialize a -> Either String a | ||
94 | decodePlain (Plain bs) = decode bs | ||
95 | |||
96 | encodePlain :: Serialize a => a -> Plain Serialize a | ||
97 | encodePlain a = Plain $ encode a | ||
98 | |||
99 | storePlain :: Storable a => a -> IO (Plain Storable a) | ||
100 | storePlain a = Plain <$> BA.create (sizeOf a) (`poke` a) | ||
101 | |||
102 | retrievePlain :: Storable a => Plain Storable a -> IO a | ||
103 | retrievePlain (Plain bs) = BA.withByteArray bs peek | ||
104 | |||
105 | data State = State Poly1305.State XSalsa.State | ||
106 | |||
107 | decrypt :: State -> Encrypted a -> Either String (Plain s a) | ||
108 | decrypt (State hash crypt) ciphertext | ||
109 | | (a == mac) = Right (Plain m) | ||
110 | | otherwise = Left "decipherAndAuth: auth fail" | ||
111 | where | ||
112 | (mac, c) = authAndBytes ciphertext | ||
113 | m = fst . XSalsa.combine crypt $ c | ||
114 | a = Auth . Poly1305.finalize . Poly1305.update hash $ c | ||
115 | |||
116 | -- Encrypt-then-Mac: Encrypt the cleartext, then compute the MAC on the | ||
117 | -- ciphertext, and prepend it to the ciphertext | ||
118 | encrypt :: State -> Plain s a -> Encrypted a | ||
119 | encrypt (State hash crypt) (Plain m) = Encrypted $ B.append (encode a) c | ||
120 | where | ||
121 | c = fst . XSalsa.combine crypt $ m | ||
122 | a = Auth . Poly1305.finalize . Poly1305.update hash $ c | ||
123 | |||
124 | -- (Poly1305.State, XSalsa.State) | ||
125 | computeSharedSecret :: SecretKey -> PublicKey -> Nonce24 -> State | ||
126 | computeSharedSecret sk recipient nonce = State hash crypt | ||
127 | where | ||
128 | -- diffie helman | ||
129 | shared = ecdh (Proxy :: Proxy Curve_X25519) sk recipient | ||
130 | -- shared secret XSalsa key | ||
131 | k = hsalsa20 shared zeros24 | ||
132 | -- cipher state | ||
133 | st0 = XSalsa.initialize 20 k nonce | ||
134 | -- Poly1305 key | ||
135 | (rs, crypt) = XSalsa.combine st0 zs where Nonce32 zs = zeros32 | ||
136 | -- Since rs is 32 bytes, this pattern should never fail... | ||
137 | Cryptonite.CryptoPassed hash = Poly1305.initialize rs | ||
138 | |||
139 | hsalsa20 k n = BA.append a b | ||
140 | where | ||
141 | Salsa.State st = XSalsa.initialize 20 k n | ||
142 | (_, as) = BA.splitAt 4 st | ||
143 | (a, xs) = BA.splitAt 16 as | ||
144 | (_, bs) = BA.splitAt 24 xs | ||
145 | (b, _ ) = BA.splitAt 16 bs | ||
146 | |||
147 | |||
148 | newtype Nonce24 = Nonce24 ByteString | ||
149 | deriving (Eq, Ord, ByteArrayAccess,Data) | ||
150 | |||
151 | quoted :: ShowS -> ShowS | ||
152 | quoted shows s = '"':shows ('"':s) | ||
153 | |||
154 | bin2hex :: ByteArrayAccess bs => bs -> String | ||
155 | bin2hex = C8.unpack . Base16.encode . BA.convert | ||
156 | |||
157 | instance Show Nonce24 where | ||
158 | showsPrec d nonce = quoted (mappend $ bin2hex nonce) | ||
159 | |||
160 | instance Sized Nonce24 where size = ConstSize 24 | ||
161 | |||
162 | instance Serialize Nonce24 where | ||
163 | get = Nonce24 <$> getBytes 24 | ||
164 | put (Nonce24 bs) = putByteString bs | ||
165 | |||
166 | newtype Nonce8 = Nonce8 Word64 | ||
167 | deriving (Eq, Ord, Data, Serialize) | ||
168 | |||
169 | -- Note: Big-endian to match Serialize instance. | ||
170 | instance Storable Nonce8 where | ||
171 | sizeOf _ = 8 | ||
172 | alignment _ = alignment (undefined::Word64) | ||
173 | peek ptr = Nonce8 . fromBE64 <$> peek (castPtr ptr) | ||
174 | poke ptr (Nonce8 w) = poke (castPtr ptr) (toBE64 w) | ||
175 | |||
176 | instance Sized Nonce8 where size = ConstSize 8 | ||
177 | |||
178 | instance ByteArrayAccess Nonce8 where | ||
179 | length _ = 8 | ||
180 | withByteArray (Nonce8 w64) kont = | ||
181 | allocaBytes 8 $ \p -> do | ||
182 | poke (castPtr p :: Ptr Word64) $ toBE64 w64 | ||
183 | kont p | ||
184 | |||
185 | instance Show Nonce8 where | ||
186 | showsPrec d nonce = quoted (mappend $ bin2hex nonce) | ||
187 | |||
188 | |||
189 | newtype Nonce32 = Nonce32 ByteString | ||
190 | deriving (Eq, Ord, ByteArrayAccess, Data) | ||
191 | |||
192 | instance Serialize Nonce32 where | ||
193 | get = Nonce32 <$> getBytes 32 | ||
194 | put (Nonce32 bs) = putByteString bs | ||
195 | |||
196 | instance Sized Nonce32 where size = ConstSize 32 | ||
197 | |||
198 | |||
199 | zeros32 :: Nonce32 | ||
200 | zeros32 = Nonce32 $ BA.replicate 32 0 | ||
201 | |||
202 | zeros24 :: ByteString | ||
203 | zeros24 = BA.take 24 zs where Nonce32 zs = zeros32 | ||
diff --git a/ToxData.hs b/ToxData.hs new file mode 100644 index 00000000..06a9b3b8 --- /dev/null +++ b/ToxData.hs | |||
@@ -0,0 +1,23 @@ | |||
1 | module ToxData where | ||
2 | |||
3 | import Crypto | ||
4 | |||
5 | -- data DHTPacketKind = Ping | Pong | GetNodes | SendNodes | ||
6 | data DHTPacket a = DHTPacket | ||
7 | { dhtSender :: PublicKey | ||
8 | , dhtEncrypted :: WithNonce24 (Encrypted (WithNonce8 a)) | ||
9 | } | ||
10 | |||
11 | data WithNonce8 a = WithNonce8 a Nonce8 | ||
12 | |||
13 | data WithNonce24 a = WithNonce24 a Nonce24 | ||
14 | |||
15 | data AliasedRequest a = AliasedRequest | ||
16 | { alias :: PublicKey | ||
17 | , aliasPayload :: WithNonce24 (Encrypted (WithNonce8 a)) | ||
18 | } | ||
19 | |||
20 | data ForwardedRequest a = ForwardedRequest | ||
21 | { forwardTo :: PublicKey | ||
22 | , forwarded :: AliasedRequest a | ||
23 | } | ||
diff --git a/ToxPacket.hs b/ToxPacket.hs new file mode 100644 index 00000000..d10a7597 --- /dev/null +++ b/ToxPacket.hs | |||
@@ -0,0 +1,76 @@ | |||
1 | {-# LANGUAGE BangPatterns #-} | ||
2 | {-# LANGUAGE CPP #-} | ||
3 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | ||
4 | {-# LANGUAGE TupleSections #-} | ||
5 | module ToxPacket where | ||
6 | |||
7 | import ToxCrypto | ||
8 | import Data.Serialize as S | ||
9 | import Data.Aeson as JSON | ||
10 | import Data.IP | ||
11 | import qualified Data.ByteString.Char8 as C8 | ||
12 | import qualified Data.ByteString as B | ||
13 | import Data.Word | ||
14 | import qualified Data.ByteString.Base16 as Base16 | ||
15 | import Network.Socket | ||
16 | import Data.ByteArray (ByteArrayAccess) | ||
17 | import qualified Data.ByteArray as BA | ||
18 | import Data.Hashable | ||
19 | import Data.Bits | ||
20 | import System.IO.Unsafe | ||
21 | import qualified Text.ParserCombinators.ReadP as RP | ||
22 | import Foreign.Storable | ||
23 | import ToxAddress -- import Network.Address hiding (nodePort,nodeInfo) | ||
24 | import Text.Read | ||
25 | import Control.Applicative | ||
26 | import Data.Char | ||
27 | import Control.Monad | ||
28 | import Crypto.Error.Types (throwCryptoError) | ||
29 | |||
30 | -- ## DHT Request packets | ||
31 | -- | ||
32 | -- | Length | Contents | | ||
33 | -- |:-------|:--------------------------| | ||
34 | -- | `1` | `uint8_t` (0x20) | | ||
35 | -- | `32` | receiver's DHT public key | | ||
36 | -- ... ... | ||
37 | |||
38 | data DHTRequestPacket = DHTRequestPacket | ||
39 | { requestTarget :: PublicKey | ||
40 | , request :: Assym (Encrypted DHTRequest) | ||
41 | } | ||
42 | |||
43 | instance Serialize DHTRequestPacket where | ||
44 | get = _todo | ||
45 | put = _todo | ||
46 | |||
47 | |||
48 | data DHTRequest | ||
49 | = NATPing Nonce8 | ||
50 | | NATPong Nonce8 | ||
51 | | DHTPK DHTPublicKey | ||
52 | |||
53 | -- | Length | Contents | | ||
54 | -- |:------------|:------------------------------------| | ||
55 | -- | `1` | `uint8_t` (0x9c) | | ||
56 | -- | `8` | `uint64_t` `no_replay` | | ||
57 | -- | `32` | Our DHT public key | | ||
58 | -- | `[39, 204]` | Maximum of 4 nodes in packed format | | ||
59 | data DHTPublicKey = DHTPublicKey | ||
60 | { dhtpkNonce :: Nonce8 | ||
61 | , dhtpk :: PublicKey | ||
62 | , dhtpkNodes :: SendNodes | ||
63 | } | ||
64 | |||
65 | -- | `32` | sender's DHT public key | | ||
66 | -- | `24` | nonce | | ||
67 | -- | `?` | encrypted message | | ||
68 | data Assym a = Assym | ||
69 | { senderKey :: PublicKey | ||
70 | , assymNonce :: Nonce24 | ||
71 | , assymData :: a | ||
72 | } | ||
73 | |||
74 | newtype SendNodes = SendNodes [NodeInfo] | ||
75 | deriving (Eq,Ord,Show,Read) | ||
76 | |||
diff --git a/ToxTransport.hs b/ToxTransport.hs new file mode 100644 index 00000000..1b2bcbe4 --- /dev/null +++ b/ToxTransport.hs | |||
@@ -0,0 +1,199 @@ | |||
1 | {-# LANGUAGE ScopedTypeVariables #-} | ||
2 | {-# LANGUAGE DataKinds,KindSignatures #-} | ||
3 | {-# LANGUAGE GADTs #-} | ||
4 | {-# LANGUAGE TypeOperators #-} | ||
5 | module ToxTransport where | ||
6 | |||
7 | import Network.QueryResponse | ||
8 | import ToxCrypto | ||
9 | import ToxAddress as Tox hiding (ReturnPath) | ||
10 | import ToxPacket | ||
11 | |||
12 | import Control.Concurrent.STM | ||
13 | import qualified Data.ByteString as B | ||
14 | ;import Data.ByteString (ByteString) | ||
15 | import Data.Word | ||
16 | import Network.Socket | ||
17 | import Data.Serialize as S (decode, Serialize, get, put, Get, Put) | ||
18 | import GHC.TypeLits | ||
19 | import Data.Typeable | ||
20 | |||
21 | newtype SymmetricKey = SymmetricKey ByteString | ||
22 | |||
23 | data TransportCrypto = TransportCrypto | ||
24 | { transportSecret :: SecretKey | ||
25 | , transportPublic :: PublicKey | ||
26 | , transportSymmetric :: STM SymmetricKey | ||
27 | } | ||
28 | |||
29 | transportDecrypt :: TransportCrypto -> Assym (Encrypted a) -> Either String a | ||
30 | transportDecrypt = _todo | ||
31 | |||
32 | -- layer :: TransportCrypto | ||
33 | -- -> Transport String SockAddr ByteString | ||
34 | -- -> Transport String Tox.Address ByteString | ||
35 | -- layer crypto = layerTransport (toxParse crypto) (toxEncode crypto) | ||
36 | |||
37 | toxEncode :: TransportCrypto -> ByteString -> Tox.Address -> (ByteString, SockAddr) | ||
38 | toxEncode = _todo | ||
39 | |||
40 | -- toxParse :: TransportCrypto -> ByteString -> SockAddr -> Either String (ByteString, Tox.Address) | ||
41 | -- toxParse crypto bs saddr = case B.head bs of _todo | ||
42 | |||
43 | data Message = Todo | DHTReq DHTRequest | AnnounceReq AnnounceRequest | ||
44 | |||
45 | -- awaitMessage :: forall a. (Maybe (Either err (x, addr)) -> IO a) -> IO a | ||
46 | |||
47 | toxTransport :: TransportCrypto -> Transport String SockAddr ByteString -> Transport String Tox.Address Message | ||
48 | toxTransport crypto (Transport await send close) = Transport await' send' close | ||
49 | where | ||
50 | await' :: HandleHi a -> IO a | ||
51 | await' forMe = fix $ await . handleOnion crypto forMe | ||
52 | |||
53 | send' = _todo | ||
54 | |||
55 | type HandleHi a = Maybe (Either String (Message, Tox.Address)) -> IO a | ||
56 | type HandleLo a = Maybe (Either String (ByteString, SockAddr)) -> IO a | ||
57 | |||
58 | -- Byte value Packet Kind Return address | ||
59 | -- :----------- :-------------------- | ||
60 | -- `0x00` Ping Request DHTNode | ||
61 | -- `0x01` Ping Response - | ||
62 | -- `0x02` Nodes Request DHTNode | ||
63 | -- `0x04` Nodes Response - | ||
64 | -- `0x18` Cookie Request DHTNode, but without sending pubkey | ||
65 | -- `0x19` Cookie Response - | ||
66 | -- | ||
67 | -- `0x21` LAN Discovery DHTNode (No reply, port 33445, trigger Nodes Request/Response) | ||
68 | -- | ||
69 | -- `0x20` DHT Request DHTNode/-forward | ||
70 | -- | ||
71 | -- `0x1a` Crypto Handshake CookieAddress | ||
72 | -- | ||
73 | -- `0x1b` Crypto Data SessionAddress | ||
74 | -- | ||
75 | -- `0x83` Announce Request OnionToOwner | ||
76 | -- `0x84` Announce Response - | ||
77 | -- `0x85` Onion Data Request OnionToOwner | ||
78 | -- `0x86` Onion Data Response - | ||
79 | -- | ||
80 | -- `0xf0` Bootstrap Info SockAddr? | ||
81 | -- | ||
82 | -- `0x80` Onion Request 0 -forward | ||
83 | -- `0x81` Onion Request 1 -forward | ||
84 | -- `0x82` Onion Request 2 -forward | ||
85 | -- `0x8c` Onion Response 3 -return | ||
86 | -- `0x8d` Onion Response 2 -return | ||
87 | -- `0x8e` Onion Response 1 -return | ||
88 | |||
89 | handleOnion :: forall a. TransportCrypto -> HandleHi a -> IO a -> HandleLo a | ||
90 | handleOnion crypto forMe forThem (Just (Right (bs,saddr))) = case B.head bs of | ||
91 | 0x20 -> forward forMe bs $ handleDHTRequest crypto saddr forMe forThem | ||
92 | 0x80 -> forward forMe bs $ handleOnionRequest (Proxy :: Proxy 0) crypto saddr forThem | ||
93 | 0x81 -> forward forMe bs $ handleOnionRequest (Proxy :: Proxy 1) crypto saddr forThem | ||
94 | 0x82 -> forward forMe bs $ handleOnionRequest (Proxy :: Proxy 2) crypto saddr forThem | ||
95 | 0x8c -> forward forMe bs $ handleOnionResponse (Proxy :: Proxy 3) crypto saddr forThem | ||
96 | 0x8d -> forward forMe bs $ handleOnionResponse (Proxy :: Proxy 2) crypto saddr forThem | ||
97 | 0x8e -> forward forMe bs $ handleOnionResponse (Proxy :: Proxy 1) crypto saddr forThem | ||
98 | typ -> go typ (B.tail bs) | ||
99 | where | ||
100 | go :: Word8 -> ByteString -> IO a | ||
101 | go typ bs = forMe $ Just (parseMessage typ bs) | ||
102 | |||
103 | forward :: forall c b b1. | ||
104 | Serialize b => | ||
105 | (Maybe (Either String b1) -> c) -> ByteString -> (b -> c) -> c | ||
106 | forward forMe bs f = either (forMe . Just . Left) f $ decode $ B.tail bs | ||
107 | |||
108 | parseMessage :: Word8 -> ByteString -> Either String (Message,Address) | ||
109 | -- Announce :: Aliased Assymetric -> ReturnPath 3 -> Packet --0x83 | ||
110 | parseMessage 0x83 bs = _todo -- Announce Request OnionToOwner | ||
111 | parseMessage _ _ = _todo | ||
112 | |||
113 | handleDHTRequest :: forall a. TransportCrypto -> SockAddr -> HandleHi a -> IO a -> DHTRequestPacket -> IO a | ||
114 | handleDHTRequest crypto saddr forMe forThem (DHTRequestPacket target payload) | ||
115 | | target == transportPublic crypto = forMe' payload | ||
116 | | otherwise = _todo -- lookup target in close list, forward message | ||
117 | >> forThem | ||
118 | where | ||
119 | forMe' :: Assym (Encrypted DHTRequest) -> IO a | ||
120 | forMe' payload = do | ||
121 | case (,) <$> transportDecrypt crypto payload <*> eaddr of | ||
122 | Left e -> forMe (Just (Left e)) | ||
123 | Right (p,addr) -> forMe (Just (Right (DHTReq p,addr))) | ||
124 | |||
125 | eaddr :: Either String Tox.Address | ||
126 | eaddr = fmap DHTNode $ nodeInfo (NodeId $ senderKey payload) saddr | ||
127 | |||
128 | data Attributed a = Attributed | ||
129 | { author :: PublicKey | ||
130 | , attributedNonce :: Nonce24 | ||
131 | , attributed :: a | ||
132 | } | ||
133 | |||
134 | -- `0x83` Announce Request OnionToOwner | ||
135 | -- `0x85` Onion Data Request OnionToOwner | ||
136 | data OPacket -- payload of an onion request | ||
137 | |||
138 | |||
139 | -- `0x84` Announce Response - | ||
140 | -- `0x86` Onion Data Response - | ||
141 | data RPacket -- payload of an onion response | ||
142 | |||
143 | -- n = 0, 1, 2 | ||
144 | data OnionRequest (n :: Nat) = OnionRequest | ||
145 | { onionNonce :: Nonce24 | ||
146 | , onionForward :: Forwarding (3 - n) OPacket | ||
147 | , pathFromOwner :: ReturnPath n | ||
148 | } | ||
149 | |||
150 | instance Serialize (OnionRequest n) where { get = _todo; put = _todo } | ||
151 | instance Serialize (OnionResponse n) where { get = _todo; put = _todo } | ||
152 | |||
153 | -- n = 1, 2, 3 | ||
154 | -- Attributed (Encrypted ( | ||
155 | |||
156 | data OnionResponse (n :: Nat) = OnionResponse | ||
157 | { pathToOwner :: ReturnPath n | ||
158 | , msgToOwner :: RPacket | ||
159 | } | ||
160 | |||
161 | data Addressed a = Addressed { sockAddr :: SockAddr, unaddressed :: a } | ||
162 | |||
163 | data ReturnPath (n :: Nat) where | ||
164 | NoReturnPath :: ReturnPath 0 | ||
165 | ReturnPath :: Nonce24 -> Encrypted (Addressed (ReturnPath n)) -> ReturnPath (n + 1) | ||
166 | |||
167 | data Forwarding (n :: Nat) msg where | ||
168 | NotForwarded :: msg -> Forwarding 0 msg | ||
169 | Forwarding :: Attributed (Encrypted (Addressed (Forwarding n msg))) -> Forwarding (n + 1) msg | ||
170 | |||
171 | handleOnionRequest :: KnownNat n => proxy n -> TransportCrypto -> SockAddr -> IO a -> OnionRequest n -> IO a | ||
172 | handleOnionRequest = _todo | ||
173 | |||
174 | handleOnionResponse :: KnownNat n => proxy n -> TransportCrypto -> SockAddr -> IO a -> OnionResponse n -> IO a | ||
175 | handleOnionResponse = _todo | ||
176 | |||
177 | {- | ||
178 | data Size a = ConstSize Int | ||
179 | | VarSize (a -> Int) | ||
180 | |||
181 | data PacketChunk a | ||
182 | = Plain a | ||
183 | | Assymetric a | ||
184 | | Symmetric a | ||
185 | -} | ||
186 | |||
187 | |||
188 | data AnnounceRequest = AnnounceRequest | ||
189 | { announcePingId :: Nonce32 -- Ping ID | ||
190 | , announceSeeking :: NodeId -- Public key we are searching for | ||
191 | , announceKey :: NodeId -- Public key that we want those sending back data packets to use | ||
192 | } | ||
193 | |||
194 | instance S.Serialize AnnounceRequest where | ||
195 | get = AnnounceRequest <$> S.get <*> S.get <*> S.get | ||
196 | put (AnnounceRequest p s k) = S.put (p,s,k) | ||
197 | |||
198 | getOnionRequest :: Get (Assym (Encrypted msg), ReturnPath 3) | ||
199 | getOnionRequest = _todo | ||
@@ -2,4 +2,4 @@ | |||
2 | defs="-DBENCODE_AESON -DTHREAD_DEBUG" | 2 | defs="-DBENCODE_AESON -DTHREAD_DEBUG" |
3 | hide="-hide-package crypto-random -hide-package crypto-api -hide-package crypto-numbers -hide-package cryptohash -hide-package prettyclass" | 3 | hide="-hide-package crypto-random -hide-package crypto-api -hide-package crypto-numbers -hide-package cryptohash -hide-package prettyclass" |
4 | cbits="cbits/*.c" | 4 | cbits="cbits/*.c" |
5 | ghc -freverse-errors $hide -isrc -XOverloadedStrings -XRecordWildCards $defs $cbits "$@" | 5 | ghc -fdefer-typed-holes -Wno-typed-holes -freverse-errors $hide -isrc -XOverloadedStrings -XRecordWildCards $defs $cbits "$@" |