diff options
Diffstat (limited to 'src/Network/Tox/DHT/Transport.hs')
-rw-r--r-- | src/Network/Tox/DHT/Transport.hs | 370 |
1 files changed, 370 insertions, 0 deletions
diff --git a/src/Network/Tox/DHT/Transport.hs b/src/Network/Tox/DHT/Transport.hs new file mode 100644 index 00000000..5a2d8a84 --- /dev/null +++ b/src/Network/Tox/DHT/Transport.hs | |||
@@ -0,0 +1,370 @@ | |||
1 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | ||
2 | {-# LANGUAGE KindSignatures #-} | ||
3 | {-# LANGUAGE LambdaCase #-} | ||
4 | {-# LANGUAGE RankNTypes #-} | ||
5 | {-# LANGUAGE StandaloneDeriving #-} | ||
6 | {-# LANGUAGE TupleSections #-} | ||
7 | {-# LANGUAGE TypeOperators #-} | ||
8 | {-# LANGUAGE UndecidableInstances #-} | ||
9 | module Network.Tox.DHT.Transport | ||
10 | ( parseDHTAddr | ||
11 | , encodeDHTAddr | ||
12 | , forwardDHTRequests | ||
13 | , module Network.Tox.Address | ||
14 | , DHTMessage(..) | ||
15 | , Ping(..) | ||
16 | , Pong(..) | ||
17 | , GetNodes(..) | ||
18 | , SendNodes(..) | ||
19 | , DHTPublicKey | ||
20 | , CookieRequest | ||
21 | , Cookie | ||
22 | , DHTRequest | ||
23 | , mapMessage | ||
24 | , encrypt | ||
25 | , decrypt | ||
26 | , dhtMessageType | ||
27 | ) where | ||
28 | |||
29 | import Network.Tox.Address | ||
30 | import Crypto.Tox hiding (encrypt,decrypt) | ||
31 | import qualified Crypto.Tox as ToxCrypto | ||
32 | import Network.QueryResponse | ||
33 | |||
34 | import Control.Arrow | ||
35 | import Control.Monad | ||
36 | import Data.Bool | ||
37 | import qualified Data.ByteString as B | ||
38 | ;import Data.ByteString (ByteString) | ||
39 | import Data.Tuple | ||
40 | import Data.Serialize as S | ||
41 | import Data.Word | ||
42 | import Network.Socket | ||
43 | |||
44 | type DHTTransport = Transport String NodeInfo (DHTMessage Encrypted8) | ||
45 | type HandleHi a = Maybe (Either String (DHTMessage Encrypted8, NodeInfo)) -> IO a | ||
46 | |||
47 | |||
48 | data DHTMessage (f :: * -> *) | ||
49 | = DHTPing (Assym (f Ping)) | ||
50 | | DHTPong (Assym (f Pong)) | ||
51 | | DHTGetNodes (Assym (f GetNodes)) | ||
52 | | DHTSendNodes (Assym (f SendNodes)) | ||
53 | | DHTCookieRequest (Assym (f CookieRequest)) | ||
54 | | DHTCookie Nonce24 (f Cookie) | ||
55 | | DHTDHTRequest PublicKey (Assym (f DHTRequest)) | ||
56 | |||
57 | deriving instance ( Show (f Cookie) | ||
58 | , Show (Assym (f Ping)) | ||
59 | , Show (Assym (f Pong)) | ||
60 | , Show (Assym (f GetNodes)) | ||
61 | , Show (Assym (f SendNodes)) | ||
62 | , Show (Assym (f CookieRequest)) | ||
63 | , Show (Assym (f DHTRequest)) | ||
64 | ) => Show (DHTMessage f) | ||
65 | |||
66 | mapMessage :: forall f b. (forall a. Nonce24 -> f a -> b) -> DHTMessage f -> b | ||
67 | mapMessage f (DHTPing a) = f (assymNonce a) (assymData a) | ||
68 | mapMessage f (DHTPong a) = f (assymNonce a) (assymData a) | ||
69 | mapMessage f (DHTGetNodes a) = f (assymNonce a) (assymData a) | ||
70 | mapMessage f (DHTSendNodes a) = f (assymNonce a) (assymData a) | ||
71 | mapMessage f (DHTCookieRequest a) = f (assymNonce a) (assymData a) | ||
72 | mapMessage f (DHTDHTRequest _ a) = f (assymNonce a) (assymData a) | ||
73 | mapMessage f (DHTCookie nonce fcookie) = f nonce fcookie | ||
74 | |||
75 | |||
76 | instance Sized Ping where size = ConstSize 1 | ||
77 | instance Sized Pong where size = ConstSize 1 | ||
78 | |||
79 | parseDHTAddr :: (ByteString, SockAddr) -> Either (DHTMessage Encrypted8,NodeInfo) (ByteString,SockAddr) | ||
80 | parseDHTAddr (msg,saddr) | ||
81 | | Just (typ,bs) <- B.uncons msg | ||
82 | , let right = Right (msg,saddr) | ||
83 | left = either (const right) Left | ||
84 | = case typ of | ||
85 | 0x00 -> left $ direct bs saddr DHTPing | ||
86 | 0x01 -> left $ direct bs saddr DHTPong | ||
87 | 0x02 -> left $ direct bs saddr DHTGetNodes | ||
88 | 0x04 -> left $ direct bs saddr DHTSendNodes | ||
89 | 0x18 -> left $ direct bs saddr DHTCookieRequest | ||
90 | 0x19 -> left $ fanGet bs getCookie (uncurry DHTCookie) (const $ noReplyAddr saddr) | ||
91 | 0x20 -> left $ fanGet bs getDHTReqest (uncurry DHTDHTRequest) (asymNodeInfo saddr . snd) | ||
92 | _ -> right | ||
93 | |||
94 | encodeDHTAddr :: (DHTMessage Encrypted8,NodeInfo) -> (ByteString, SockAddr) | ||
95 | encodeDHTAddr (msg,ni) = (runPut $ putMessage msg, nodeAddr ni) | ||
96 | |||
97 | dhtMessageType :: DHTMessage Encrypted8 -> ( Word8, Put ) | ||
98 | dhtMessageType (DHTPing a) = (0x00, putAssym a) | ||
99 | dhtMessageType (DHTPong a) = (0x01, putAssym a) | ||
100 | dhtMessageType (DHTGetNodes a) = (0x02, putAssym a) | ||
101 | dhtMessageType (DHTSendNodes a) = (0x04, putAssym a) | ||
102 | dhtMessageType (DHTCookieRequest a) = (0x18, putAssym a) | ||
103 | dhtMessageType (DHTCookie n x) = (0x19, put n >> put x) | ||
104 | dhtMessageType (DHTDHTRequest k a) = (0x20, putPublicKey k >> putAssym a) | ||
105 | |||
106 | putMessage :: DHTMessage Encrypted8 -> Put | ||
107 | putMessage msg = case dhtMessageType msg of (typ,p) -> put typ >> p | ||
108 | |||
109 | getCookie :: Get (Nonce24, Encrypted8 Cookie) | ||
110 | getCookie = get | ||
111 | |||
112 | getDHTReqest :: Get (PublicKey, Assym (Encrypted8 DHTRequest)) | ||
113 | getDHTReqest = (,) <$> getPublicKey <*> getAssym | ||
114 | |||
115 | -- ## DHT Request packets | ||
116 | -- | ||
117 | -- | Length | Contents | | ||
118 | -- |:-------|:--------------------------| | ||
119 | -- | `1` | `uint8_t` (0x20) | | ||
120 | -- | `32` | receiver's DHT public key | | ||
121 | -- ... ... | ||
122 | |||
123 | |||
124 | getDHT :: Sized a => Get (Assym (Encrypted8 a)) | ||
125 | getDHT = getAssym | ||
126 | |||
127 | |||
128 | -- Throws an error if called with a non-internet socket. | ||
129 | direct :: Sized a => ByteString | ||
130 | -> SockAddr | ||
131 | -> (Assym (Encrypted8 a) -> DHTMessage Encrypted8) | ||
132 | -> Either String (DHTMessage Encrypted8, NodeInfo) | ||
133 | direct bs saddr f = fanGet bs getDHT f (asymNodeInfo saddr) | ||
134 | |||
135 | -- Throws an error if called with a non-internet socket. | ||
136 | asymNodeInfo :: SockAddr -> Assym a -> NodeInfo | ||
137 | asymNodeInfo saddr asym = either (error . mappend "asymNodeInfo: ") id $ nodeInfo (key2id $ senderKey asym) saddr | ||
138 | |||
139 | |||
140 | fanGet :: ByteString -> Get x -> (x -> a) -> (x -> b) -> Either String (a,b) | ||
141 | fanGet bs getIt f nid = fmap (f &&& nid) $ runGet getIt bs | ||
142 | |||
143 | -- Throws an error if called with a non-internet socket. | ||
144 | noReplyAddr :: SockAddr -> NodeInfo | ||
145 | noReplyAddr saddr = either (error . mappend "noReplyAddr: ") id $ nodeInfo zeroID saddr | ||
146 | |||
147 | |||
148 | data DHTRequest | ||
149 | -- #### NAT ping request | ||
150 | -- | ||
151 | -- Length Contents | ||
152 | -- :------- :------------------------- | ||
153 | -- `1` `uint8_t` (0xfe) | ||
154 | -- `1` `uint8_t` (0x00) | ||
155 | -- `8` `uint64_t` random number | ||
156 | = NATPing Nonce8 | ||
157 | -- #### NAT ping response | ||
158 | -- | ||
159 | -- Length Contents | ||
160 | -- :------- :----------------------------------------------------------------- | ||
161 | -- `1` `uint8_t` (0xfe) | ||
162 | -- `1` `uint8_t` (0x01) | ||
163 | -- `8` `uint64_t` random number (the same that was received in request) | ||
164 | | NATPong Nonce8 | ||
165 | | DHTPK LongTermKeyWrap | ||
166 | deriving Show | ||
167 | |||
168 | instance Sized DHTRequest where | ||
169 | size = VarSize $ \case | ||
170 | NATPing _ -> 10 | ||
171 | NATPong _ -> 10 | ||
172 | DHTPK wrap -> 1{-typ-} + 32{-key-} + 24{-nonce-} | ||
173 | + case size of | ||
174 | ConstSize n -> n | ||
175 | VarSize f -> f (wrapData wrap) | ||
176 | |||
177 | instance Serialize DHTRequest where | ||
178 | get = do | ||
179 | tag <- get | ||
180 | case tag :: Word8 of | ||
181 | 0xfe -> do | ||
182 | direction <- get | ||
183 | bool NATPong NATPing (direction==(0::Word8)) <$> get | ||
184 | 0x9c -> DHTPK <$> get | ||
185 | _ -> fail ("unrecognized DHT request: "++show tag) | ||
186 | put (NATPing n) = put (0xfe00 :: Word16) >> put n | ||
187 | put (NATPong n) = put (0xfe01 :: Word16) >> put n | ||
188 | put (DHTPK pk) = put (0x9c :: Word8) >> put pk | ||
189 | |||
190 | -- DHT public key packet: | ||
191 | -- (As Onion data packet?) | ||
192 | -- | ||
193 | -- | Length | Contents | | ||
194 | -- |:------------|:------------------------------------| | ||
195 | -- | `1` | `uint8_t` (0x9c) | | ||
196 | -- | `8` | `uint64_t` `no_replay` | | ||
197 | -- | `32` | Our DHT public key | | ||
198 | -- | `[39, 204]` | Maximum of 4 nodes in packed format | | ||
199 | data DHTPublicKey = DHTPublicKey | ||
200 | { dhtpkNonce :: Nonce8 -- no_replay | ||
201 | , dhtpk :: PublicKey -- dht public key | ||
202 | , dhtpkNodes :: SendNodes -- other reachable nodes | ||
203 | } | ||
204 | |||
205 | -- When sent as a DHT request packet (this is the data sent in the DHT request | ||
206 | -- packet): | ||
207 | -- | ||
208 | -- Length Contents | ||
209 | -- :--------- :------------------------------- | ||
210 | -- `1` `uint8_t` (0x9c) | ||
211 | -- `32` Long term public key of sender | ||
212 | -- `24` Nonce | ||
213 | -- variable Encrypted payload | ||
214 | data LongTermKeyWrap = LongTermKeyWrap | ||
215 | { wrapLongTermKey :: PublicKey | ||
216 | , wrapNonce :: Nonce24 | ||
217 | , wrapData :: Encrypted DHTPublicKey | ||
218 | } | ||
219 | deriving Show | ||
220 | |||
221 | instance Serialize LongTermKeyWrap where | ||
222 | get = LongTermKeyWrap <$> getPublicKey <*> get <*> get | ||
223 | put (LongTermKeyWrap key nonce dta) = putPublicKey key >> put nonce >> put dta | ||
224 | |||
225 | |||
226 | instance Sized DHTPublicKey where | ||
227 | -- NOTE: 41 bytes includes the 1-byte tag 0x9c in the size. | ||
228 | -- WARNING: Serialize instance does not include this byte FIXME | ||
229 | size = VarSize $ \(DHTPublicKey _ _ nodes) -> 41 + case size of | ||
230 | ConstSize nodes -> nodes | ||
231 | VarSize sznodes -> sznodes nodes | ||
232 | |||
233 | instance Serialize DHTPublicKey where | ||
234 | -- TODO: This should agree with Sized instance. | ||
235 | get = DHTPublicKey <$> get <*> getPublicKey <*> get | ||
236 | put (DHTPublicKey nonce key nodes) = do | ||
237 | put nonce | ||
238 | putPublicKey key | ||
239 | put nodes | ||
240 | |||
241 | newtype GetNodes = GetNodes NodeId | ||
242 | deriving (Eq,Ord,Show,Read,S.Serialize) | ||
243 | |||
244 | instance Sized GetNodes where | ||
245 | size = ConstSize 32 -- TODO This right? | ||
246 | |||
247 | newtype SendNodes = SendNodes [NodeInfo] | ||
248 | deriving (Eq,Ord,Show,Read) | ||
249 | |||
250 | instance Sized SendNodes where | ||
251 | size = VarSize $ \(SendNodes ns) -> case size of | ||
252 | ConstSize nodeFormatSize -> nodeFormatSize * length ns | ||
253 | VarSize nsize -> sum $ map nsize ns | ||
254 | |||
255 | instance S.Serialize SendNodes where | ||
256 | get = do | ||
257 | cnt <- S.get :: S.Get Word8 | ||
258 | ns <- sequence $ replicate (fromIntegral cnt) S.get | ||
259 | return $ SendNodes ns | ||
260 | |||
261 | put (SendNodes ns) = do | ||
262 | let ns' = take 4 ns | ||
263 | S.put (fromIntegral (length ns') :: Word8) | ||
264 | mapM_ S.put ns' | ||
265 | |||
266 | data Ping = Ping deriving Show | ||
267 | data Pong = Pong deriving Show | ||
268 | |||
269 | instance S.Serialize Ping where | ||
270 | get = do w8 <- S.get | ||
271 | if (w8 :: Word8) /= 0 | ||
272 | then fail "Malformed ping." | ||
273 | else return Ping | ||
274 | put Ping = S.put (0 :: Word8) | ||
275 | |||
276 | instance S.Serialize Pong where | ||
277 | get = do w8 <- S.get | ||
278 | if (w8 :: Word8) /= 1 | ||
279 | then fail "Malformed pong." | ||
280 | else return Pong | ||
281 | put Pong = S.put (1 :: Word8) | ||
282 | |||
283 | newtype CookieRequest = CookieRequest PublicKey | ||
284 | deriving (Eq, Show) | ||
285 | newtype CookieResponse = CookieResponse Cookie | ||
286 | deriving (Eq, Show) | ||
287 | |||
288 | data Cookie = Cookie Nonce24 (Encrypted CookieData) | ||
289 | deriving (Eq, Ord, Show) | ||
290 | |||
291 | instance Sized Cookie where size = ConstSize 112 -- 24 byte nonce + 88 byte cookie data | ||
292 | |||
293 | instance Serialize Cookie where | ||
294 | get = Cookie <$> get <*> get | ||
295 | put (Cookie nonce dta) = put nonce >> put dta | ||
296 | |||
297 | data CookieData = CookieData -- 16 (mac) | ||
298 | { cookieTime :: Word64 -- 8 | ||
299 | , longTermKey :: PublicKey -- 32 | ||
300 | , dhtKey :: PublicKey -- + 32 | ||
301 | } -- = 88 bytes when encrypted. | ||
302 | |||
303 | instance Sized CookieData where | ||
304 | size = ConstSize 72 | ||
305 | |||
306 | instance Sized CookieRequest where | ||
307 | size = ConstSize 64 -- 32 byte key + 32 byte padding | ||
308 | |||
309 | instance Serialize CookieRequest where | ||
310 | get = CookieRequest <$> getPublicKey <* {- padding -} getPublicKey | ||
311 | put (CookieRequest k) = putPublicKey k >> {- padding -} putPublicKey k | ||
312 | |||
313 | forwardDHTRequests :: TransportCrypto -> (PublicKey -> IO (Maybe NodeInfo)) -> DHTTransport -> DHTTransport | ||
314 | forwardDHTRequests crypto closeLookup dht = dht { awaitMessage = await' } | ||
315 | where | ||
316 | await' :: HandleHi a -> IO a | ||
317 | await' pass = awaitMessage dht $ \case | ||
318 | Just (Right (m@(DHTDHTRequest target payload),src)) | target /= transportPublic crypto | ||
319 | -> do mni <- closeLookup target | ||
320 | -- Forward the message if the target is in our close list. | ||
321 | forM_ mni $ \ni -> sendMessage dht ni m | ||
322 | await' pass | ||
323 | m -> pass m | ||
324 | |||
325 | encrypt :: TransportCrypto -> DHTMessage ((,) Nonce8) -> NodeInfo -> (DHTMessage Encrypted8, NodeInfo) | ||
326 | encrypt crypto msg ni = ( transcode (encryptMessage crypto (id2key $ nodeId ni)) msg | ||
327 | , ni ) | ||
328 | |||
329 | encryptMessage :: Serialize a => | ||
330 | TransportCrypto -> | ||
331 | PublicKey -> | ||
332 | Nonce24 -> Either (Nonce8,a) (Assym (Nonce8,a)) -> Encrypted8 a | ||
333 | encryptMessage crypto destKey n (Right assym) = E8 $ ToxCrypto.encrypt secret plain | ||
334 | where | ||
335 | secret = computeSharedSecret (transportSecret crypto) destKey n | ||
336 | plain = encodePlain $ swap $ assymData assym | ||
337 | encryptMessage crypto destKey n (Left plain) = _todo -- need cached public key. | ||
338 | |||
339 | decrypt :: TransportCrypto -> DHTMessage Encrypted8 -> NodeInfo -> Either String (DHTMessage ((,) Nonce8), NodeInfo) | ||
340 | decrypt crypto msg ni = (, ni) <$> (sequenceMessage $ transcode (decryptMessage crypto) msg) | ||
341 | |||
342 | decryptMessage :: Serialize x => | ||
343 | TransportCrypto | ||
344 | -> Nonce24 | ||
345 | -> Either (Encrypted8 x) (Assym (Encrypted8 x)) | ||
346 | -> (Either String ∘ ((,) Nonce8)) x | ||
347 | decryptMessage crypto n (Right assymE) = plain8 $ ToxCrypto.decrypt secret e | ||
348 | where | ||
349 | secret = computeSharedSecret (transportSecret crypto) (senderKey assymE) n | ||
350 | E8 e = assymData assymE | ||
351 | plain8 = Composed . fmap swap . (>>= decodePlain) | ||
352 | decryptMessage crypto n (Left (E8 e)) = _todo -- need cached public key | ||
353 | |||
354 | sequenceMessage :: Applicative m => DHTMessage (m ∘ f) -> m (DHTMessage f) | ||
355 | sequenceMessage (DHTPing asym) = fmap DHTPing $ sequenceA $ fmap uncomposed asym | ||
356 | sequenceMessage (DHTPong asym) = fmap DHTPong $ sequenceA $ fmap uncomposed asym | ||
357 | sequenceMessage (DHTGetNodes asym) = fmap DHTGetNodes $ sequenceA $ fmap uncomposed asym | ||
358 | sequenceMessage (DHTSendNodes asym) = fmap DHTSendNodes $ sequenceA $ fmap uncomposed asym | ||
359 | sequenceMessage (DHTCookieRequest asym) = fmap DHTCookieRequest $ sequenceA $ fmap uncomposed asym | ||
360 | sequenceMessage (DHTCookie n dta) = fmap (DHTCookie n) $ uncomposed dta | ||
361 | sequenceMessage (DHTDHTRequest pubkey asym) = fmap (DHTDHTRequest pubkey) $ sequenceA $ fmap uncomposed asym | ||
362 | |||
363 | transcode :: forall f g. (forall a. Serialize a => Nonce24 -> Either (f a) (Assym (f a)) -> g a) -> DHTMessage f -> DHTMessage g | ||
364 | transcode f (DHTPing asym) = DHTPing $ asym { assymData = f (assymNonce asym) (Right asym) } | ||
365 | transcode f (DHTPong asym) = DHTPong $ asym { assymData = f (assymNonce asym) (Right asym) } | ||
366 | transcode f (DHTGetNodes asym) = DHTGetNodes $ asym { assymData = f (assymNonce asym) (Right asym) } | ||
367 | transcode f (DHTSendNodes asym) = DHTSendNodes $ asym { assymData = f (assymNonce asym) (Right asym) } | ||
368 | transcode f (DHTCookieRequest asym) = DHTCookieRequest $ asym { assymData = f (assymNonce asym) (Right asym) } | ||
369 | transcode f (DHTCookie n dta) = DHTCookie n $ f n $ Left dta | ||
370 | transcode f (DHTDHTRequest pubkey asym) = DHTDHTRequest pubkey $ asym { assymData = f (assymNonce asym) (Right asym) } | ||