summaryrefslogtreecommitdiff
path: root/ToxCrypto.hs
diff options
context:
space:
mode:
authorjoe <joe@jerkface.net>2017-08-27 18:59:23 -0400
committerjoe <joe@jerkface.net>2017-08-27 18:59:23 -0400
commit5472805a6a8fb3c3d64cbeff5bda1d78a898c602 (patch)
tree015eed92ebbbe72d3ed07b1959dc5d15719d91b2 /ToxCrypto.hs
parent396b6daf475b1769a214e0d3ee8b476ff415d2f9 (diff)
reworking... ToxTransport and related modules.
Diffstat (limited to 'ToxCrypto.hs')
-rw-r--r--ToxCrypto.hs203
1 files changed, 203 insertions, 0 deletions
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 #-}
5module 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
22import qualified Crypto.Cipher.Salsa as Salsa
23import qualified Crypto.Cipher.XSalsa as XSalsa
24import Crypto.ECC.Class
25import qualified Crypto.Error as Cryptonite
26import qualified Crypto.MAC.Poly1305 as Poly1305
27import Crypto.PubKey.Curve25519
28import qualified Data.ByteArray as BA
29 ;import Data.ByteArray as BA (ByteArrayAccess, Bytes)
30import Data.ByteString as B
31import qualified Data.ByteString.Base16 as Base16
32import qualified Data.ByteString.Char8 as C8
33import Data.Data
34import Data.Kind
35import Data.Ord
36import Data.Serialize
37import Data.Word
38import Foreign.Marshal.Alloc
39import Foreign.Ptr
40import Foreign.Storable
41import System.Endian
42import qualified Data.ByteString.Internal
43
44-- | A 16-byte mac and an arbitrary-length encrypted stream.
45newtype Encrypted a = Encrypted ByteString
46 deriving (Eq,Ord,Data)
47
48newtype Auth = Auth Poly1305.Auth deriving (Eq, ByteArrayAccess)
49instance Ord Auth where
50 compare (Auth a) (Auth b) = comparing (BA.convert :: Poly1305.Auth -> Bytes) a b
51instance 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]
57con_Auth = mkConstr (dataTypeOf (Auth (error "con_Auth"))) "Auth" [] Prefix
58instance Serialize Auth where
59 get = Auth . Poly1305.Auth . BA.convert <$> getBytes 16
60 put (Auth (Poly1305.Auth bs)) = putByteString $ BA.convert bs
61
62encryptedAuth :: Encrypted a -> Auth
63encryptedAuth (Encrypted bs)
64 | Right auth <- decode (B.take 16 bs) = auth
65 | otherwise = error "encryptedAuth: insufficient bytes"
66
67authAndBytes :: Encrypted a -> (Auth, ByteString)
68authAndBytes (Encrypted bs) = (auth,bs')
69 where
70 (as,bs') = B.splitAt 16 bs
71 Right auth = decode as
72
73data Size a = ConstSize Int
74 | VarSize (a -> Int)
75
76class Sized a where size :: Size a
77
78instance 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
84getRemainingEncrypted :: Get (Encrypted a)
85getRemainingEncrypted = Encrypted <$> (remaining >>= getBytes)
86
87putEncrypted :: Encrypted a -> Put
88putEncrypted (Encrypted bs) = putByteString bs
89
90newtype Plain (s:: * -> Constraint) a = Plain ByteString
91
92
93decodePlain :: Serialize a => Plain Serialize a -> Either String a
94decodePlain (Plain bs) = decode bs
95
96encodePlain :: Serialize a => a -> Plain Serialize a
97encodePlain a = Plain $ encode a
98
99storePlain :: Storable a => a -> IO (Plain Storable a)
100storePlain a = Plain <$> BA.create (sizeOf a) (`poke` a)
101
102retrievePlain :: Storable a => Plain Storable a -> IO a
103retrievePlain (Plain bs) = BA.withByteArray bs peek
104
105data State = State Poly1305.State XSalsa.State
106
107decrypt :: State -> Encrypted a -> Either String (Plain s a)
108decrypt (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
118encrypt :: State -> Plain s a -> Encrypted a
119encrypt (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)
125computeSharedSecret :: SecretKey -> PublicKey -> Nonce24 -> State
126computeSharedSecret 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
139hsalsa20 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
148newtype Nonce24 = Nonce24 ByteString
149 deriving (Eq, Ord, ByteArrayAccess,Data)
150
151quoted :: ShowS -> ShowS
152quoted shows s = '"':shows ('"':s)
153
154bin2hex :: ByteArrayAccess bs => bs -> String
155bin2hex = C8.unpack . Base16.encode . BA.convert
156
157instance Show Nonce24 where
158 showsPrec d nonce = quoted (mappend $ bin2hex nonce)
159
160instance Sized Nonce24 where size = ConstSize 24
161
162instance Serialize Nonce24 where
163 get = Nonce24 <$> getBytes 24
164 put (Nonce24 bs) = putByteString bs
165
166newtype Nonce8 = Nonce8 Word64
167 deriving (Eq, Ord, Data, Serialize)
168
169-- Note: Big-endian to match Serialize instance.
170instance 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
176instance Sized Nonce8 where size = ConstSize 8
177
178instance 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
185instance Show Nonce8 where
186 showsPrec d nonce = quoted (mappend $ bin2hex nonce)
187
188
189newtype Nonce32 = Nonce32 ByteString
190 deriving (Eq, Ord, ByteArrayAccess, Data)
191
192instance Serialize Nonce32 where
193 get = Nonce32 <$> getBytes 32
194 put (Nonce32 bs) = putByteString bs
195
196instance Sized Nonce32 where size = ConstSize 32
197
198
199zeros32 :: Nonce32
200zeros32 = Nonce32 $ BA.replicate 32 0
201
202zeros24 :: ByteString
203zeros24 = BA.take 24 zs where Nonce32 zs = zeros32