1-- |
2-- Module      : Crypto.Cipher.ChaChaPoly1305
3-- License     : BSD-style
4-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
5-- Stability   : stable
6-- Portability : good
7--
8-- A simple AEAD scheme using ChaCha20 and Poly1305. See
9-- <https://tools.ietf.org/html/rfc7539 RFC 7539>.
10--
11-- The State is not modified in place, so each function changing the State,
12-- returns a new State.
13--
14-- Authenticated Data need to be added before any call to 'encrypt' or 'decrypt',
15-- and once all the data has been added, then 'finalizeAAD' need to be called.
16--
17-- Once 'finalizeAAD' has been called, no further 'appendAAD' call should be make.
18--
19-- >import Data.ByteString.Char8 as B
20-- >import Data.ByteArray
21-- >import Crypto.Error
22-- >import Crypto.Cipher.ChaChaPoly1305 as C
23-- >
24-- >encrypt
25-- >    :: ByteString -- nonce (12 random bytes)
26-- >    -> ByteString -- symmetric key
27-- >    -> ByteString -- optional associated data (won't be encrypted)
28-- >    -> ByteString -- input plaintext to be encrypted
29-- >    -> CryptoFailable ByteString -- ciphertext with a 128-bit tag attached
30-- >encrypt nonce key header plaintext = do
31-- >    st1 <- C.nonce12 nonce >>= C.initialize key
32-- >    let
33-- >        st2 = C.finalizeAAD $ C.appendAAD header st1
34-- >        (out, st3) = C.encrypt plaintext st2
35-- >        auth = C.finalize st3
36-- >    return $ out `B.append` Data.ByteArray.convert auth
37--
38module Crypto.Cipher.ChaChaPoly1305
39    ( State
40    , Nonce
41    , nonce12
42    , nonce8
43    , incrementNonce
44    , initialize
45    , appendAAD
46    , finalizeAAD
47    , encrypt
48    , decrypt
49    , finalize
50    ) where
51
52import           Control.Monad             (when)
53import           Crypto.Internal.ByteArray (ByteArrayAccess, ByteArray, Bytes, ScrubbedBytes)
54import qualified Crypto.Internal.ByteArray as B
55import           Crypto.Internal.Imports
56import           Crypto.Error
57import qualified Crypto.Cipher.ChaCha as ChaCha
58import qualified Crypto.MAC.Poly1305  as Poly1305
59import           Data.Memory.Endian
60import qualified Data.ByteArray.Pack as P
61import           Foreign.Ptr
62import           Foreign.Storable
63
64-- | A ChaChaPoly1305 State.
65--
66-- The state is immutable, and only new state can be created
67data State = State !ChaCha.State
68                   !Poly1305.State
69                   !Word64 -- AAD length
70                   !Word64 -- ciphertext length
71
72-- | Valid Nonce for ChaChaPoly1305.
73--
74-- It can be created with 'nonce8' or 'nonce12'
75data Nonce = Nonce8 Bytes | Nonce12 Bytes
76
77instance ByteArrayAccess Nonce where
78  length (Nonce8  n) = B.length n
79  length (Nonce12 n) = B.length n
80
81  withByteArray (Nonce8  n) = B.withByteArray n
82  withByteArray (Nonce12 n) = B.withByteArray n
83
84-- Based on the following pseudo code:
85--
86-- chacha20_aead_encrypt(aad, key, iv, constant, plaintext):
87--     nonce = constant | iv
88--     otk = poly1305_key_gen(key, nonce)
89--     ciphertext = chacha20_encrypt(key, 1, nonce, plaintext)
90--     mac_data = aad | pad16(aad)
91--     mac_data |= ciphertext | pad16(ciphertext)
92--     mac_data |= num_to_4_le_bytes(aad.length)
93--     mac_data |= num_to_4_le_bytes(ciphertext.length)
94--     tag = poly1305_mac(mac_data, otk)
95--     return (ciphertext, tag)
96
97pad16 :: Word64 -> Bytes
98pad16 n
99    | modLen == 0 = B.empty
100    | otherwise   = B.replicate (16 - modLen) 0
101  where
102    modLen = fromIntegral (n `mod` 16)
103
104-- | Nonce smart constructor 12 bytes IV, nonce constructor
105nonce12 :: ByteArrayAccess iv => iv -> CryptoFailable Nonce
106nonce12 iv
107    | B.length iv /= 12 = CryptoFailed CryptoError_IvSizeInvalid
108    | otherwise         = CryptoPassed . Nonce12 . B.convert $ iv
109
110-- | 8 bytes IV, nonce constructor
111nonce8 :: ByteArrayAccess ba
112       => ba -- ^ 4 bytes constant
113       -> ba -- ^ 8 bytes IV
114       -> CryptoFailable Nonce
115nonce8 constant iv
116    | B.length constant /= 4 = CryptoFailed CryptoError_IvSizeInvalid
117    | B.length iv       /= 8 = CryptoFailed CryptoError_IvSizeInvalid
118    | otherwise              = CryptoPassed . Nonce8 . B.concat $ [constant, iv]
119
120-- | Increment a nonce
121incrementNonce :: Nonce -> Nonce
122incrementNonce (Nonce8  n) = Nonce8  $ incrementNonce' n 4
123incrementNonce (Nonce12 n) = Nonce12 $ incrementNonce' n 0
124
125incrementNonce' :: Bytes -> Int -> Bytes
126incrementNonce' b offset = B.copyAndFreeze b $ \s ->
127    loop s (s `plusPtr` offset)
128    where
129      loop :: Ptr Word8 -> Ptr Word8 -> IO ()
130      loop s p
131          | s == (p `plusPtr` (B.length b - offset - 1)) = peek s >>= poke s . (+) 1
132          | otherwise = do
133              r <- (+) 1 <$> peek p
134              poke p r
135              when (r == 0) $ loop s (p `plusPtr` 1)
136
137-- | Initialize a new ChaChaPoly1305 State
138--
139-- The key length need to be 256 bits, and the nonce
140-- procured using either `nonce8` or `nonce12`
141initialize :: ByteArrayAccess key
142           => key -> Nonce -> CryptoFailable State
143initialize key (Nonce8  nonce) = initialize' key nonce
144initialize key (Nonce12 nonce) = initialize' key nonce
145
146initialize' :: ByteArrayAccess key
147            => key -> Bytes -> CryptoFailable State
148initialize' key nonce
149    | B.length key /= 32 = CryptoFailed CryptoError_KeySizeInvalid
150    | otherwise          = CryptoPassed $ State encState polyState 0 0
151  where
152    rootState           = ChaCha.initialize 20 key nonce
153    (polyKey, encState) = ChaCha.generate rootState 64
154    polyState           = throwCryptoError $ Poly1305.initialize (B.take 32 polyKey :: ScrubbedBytes)
155
156-- | Append Authenticated Data to the State and return
157-- the new modified State.
158--
159-- Once no further call to this function need to be make,
160-- the user should call 'finalizeAAD'
161appendAAD :: ByteArrayAccess ba => ba -> State -> State
162appendAAD ba (State encState macState aadLength plainLength) =
163    State encState newMacState newLength plainLength
164  where
165    newMacState = Poly1305.update macState ba
166    newLength   = aadLength + fromIntegral (B.length ba)
167
168-- | Finalize the Authenticated Data and return the finalized State
169finalizeAAD :: State -> State
170finalizeAAD (State encState macState aadLength plainLength) =
171    State encState newMacState aadLength plainLength
172  where
173    newMacState = Poly1305.update macState $ pad16 aadLength
174
175-- | Encrypt a piece of data and returns the encrypted Data and the
176-- updated State.
177encrypt :: ByteArray ba => ba -> State -> (ba, State)
178encrypt input (State encState macState aadLength plainLength) =
179    (output, State newEncState newMacState aadLength newPlainLength)
180  where
181    (output, newEncState) = ChaCha.combine encState input
182    newMacState           = Poly1305.update macState output
183    newPlainLength        = plainLength + fromIntegral (B.length input)
184
185-- | Decrypt a piece of data and returns the decrypted Data and the
186-- updated State.
187decrypt :: ByteArray ba => ba -> State -> (ba, State)
188decrypt input (State encState macState aadLength plainLength) =
189    (output, State newEncState newMacState aadLength newPlainLength)
190  where
191    (output, newEncState) = ChaCha.combine encState input
192    newMacState           = Poly1305.update macState input
193    newPlainLength        = plainLength + fromIntegral (B.length input)
194
195-- | Generate an authentication tag from the State.
196finalize :: State -> Poly1305.Auth
197finalize (State _ macState aadLength plainLength) =
198    Poly1305.finalize $ Poly1305.updates macState
199        [ pad16 plainLength
200        , either (error "finalize: internal error") id $ P.fill 16 (P.putStorable (toLE aadLength) >> P.putStorable (toLE plainLength))
201        ]
202