content: -- |
  -- Module      : Crypto.Saltine.Core.AEAD.ChaCha20Poly1305
  -- Copyright   : (c) Thomas DuBuisson 2017
  --               (c) Max Amanshauser 2021
  -- License     : MIT
  --
  -- Maintainer  : max@lambdalifting.org
  -- Stability   : experimental
  -- Portability : non-portable
  --
  -- Secret-key authenticated encryption with additional data (AEAD):
  -- "Crypto.Saltine.Core.AEAD.ChaCha20Poly1305"
  --
  -- Generating nonces for the functions in this module randomly
  -- is not recommended, due to the risk of generating collisions.
  
  module Crypto.Saltine.Core.AEAD.ChaCha20Poly1305 (
    Key, Nonce,
    aead, aeadOpen,
    aeadDetached, aeadOpenDetached,
    newKey, newNonce
    ) where
  
  import Crypto.Saltine.Internal.AEAD.ChaCha20Poly1305
              ( c_aead
              , c_aead_open
              , c_aead_detached
              , c_aead_open_detached
              , Key(..)
              , Nonce(..)
              )
  import Crypto.Saltine.Internal.Util as U
  import Data.ByteString              (ByteString)
  import Foreign.Ptr
  
  import qualified Crypto.Saltine.Internal.AEAD.ChaCha20Poly1305  as Bytes
  import qualified Data.ByteString                                as S
  
  -- | Creates a random 'ChaCha20Poly1305' key
  newKey :: IO Key
  newKey = Key <$> randomByteString Bytes.aead_chacha20poly1305_keybytes
  
  -- | Creates a random 'ChaCha20Poly1305' nonce
  newNonce :: IO Nonce
  newNonce = Nonce <$> randomByteString Bytes.aead_chacha20poly1305_npubbytes
  
  
  -- | Encrypts a message. It is infeasible for an attacker to decrypt
  -- the message so long as the 'Nonce' is never repeated.
  aead
      :: Key
      -> Nonce
      -> ByteString
      -- ^ Message
      -> ByteString
      -- ^ AAD
      -> ByteString
      -- ^ Ciphertext
  aead (Key key) (Nonce nonce) msg aad =
    snd . buildUnsafeByteString clen $ \pc ->
      constByteStrings [key, msg, aad, nonce] $ \
        [(pk, _), (pm, _), (pa, _), (pn, _)] ->
            c_aead pc nullPtr pm (fromIntegral mlen) pa (fromIntegral alen) nullPtr pn pk
    where mlen    = S.length msg
          alen    = S.length aad
          clen    = mlen + Bytes.aead_chacha20poly1305_abytes
  
  -- | Decrypts a message. Returns 'Nothing' if the keys and message do
  -- not match.
  aeadOpen
      :: Key
      -> Nonce
      -> ByteString
      -- ^ Ciphertext
      -> ByteString
      -- ^ AAD
      -> Maybe ByteString
      -- ^ Message
  aeadOpen (Key key) (Nonce nonce) cipher aad = do
    let clen   = S.length cipher
        alen   = S.length aad
    mlen <- clen `safeSubtract` Bytes.aead_chacha20poly1305_abytes
    let (err, vec) = buildUnsafeByteString mlen $ \pm ->
          constByteStrings [key, cipher, aad, nonce] $ \
            [(pk, _), (pc, _), (pa, _), (pn, _)] ->
              c_aead_open pm nullPtr nullPtr pc (fromIntegral clen) pa (fromIntegral alen) pn pk
    hush . handleErrno err $ vec
  
  -- | Encrypts a message. It is infeasible for an attacker to decrypt
  -- the message so long as the 'Nonce' is never repeated.
  aeadDetached
      :: Key
      -> Nonce
      -> ByteString
      -- ^ Message
      -> ByteString
      -- ^ AAD
      -> (ByteString,ByteString)
      -- ^ Tag, Ciphertext
  aeadDetached (Key key) (Nonce nonce) msg aad =
    buildUnsafeByteString clen $ \pc ->
     fmap snd . buildUnsafeByteString' tlen $ \pt ->
      constByteStrings [key, msg, aad, nonce] $ \
        [(pk, _), (pm, _), (pa, _), (pn, _)] ->
            c_aead_detached pc pt nullPtr pm (fromIntegral mlen) pa (fromIntegral alen) nullPtr pn pk
    where mlen    = S.length msg
          alen    = S.length aad
          clen    = mlen
          tlen    = Bytes.aead_chacha20poly1305_abytes
  
  -- | Decrypts a message. Returns 'Nothing' if the keys and message do
  -- not match.
  aeadOpenDetached
      :: Key
      -> Nonce
      -> ByteString
      -- ^ Tag
      -> ByteString
      -- ^ Ciphertext
      -> ByteString
      -- ^ AAD
      -> Maybe ByteString
      -- ^ Message
  aeadOpenDetached (Key key) (Nonce nonce) tag cipher aad
      | S.length tag /= tlen = Nothing
      | otherwise =
    let (err, vec) = buildUnsafeByteString len $ \pm ->
          constByteStrings [key, tag, cipher, aad, nonce] $ \
            [(pk, _), (pt, _), (pc, _), (pa, _), (pn, _)] ->
              c_aead_open_detached pm nullPtr pc (fromIntegral len) pt pa (fromIntegral alen) pn pk
    in hush . handleErrno err $ vec
    where len    = S.length cipher
          alen   = S.length aad
          tlen   = Bytes.aead_chacha20poly1305_abytes
  