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 /ToxCrypto.hs | |
parent | 396b6daf475b1769a214e0d3ee8b476ff415d2f9 (diff) |
reworking... ToxTransport and related modules.
Diffstat (limited to 'ToxCrypto.hs')
-rw-r--r-- | ToxCrypto.hs | 203 |
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 #-} | ||
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 | ||