-- |The HMAC construction for a cryptographic hash

{-# LANGUAGE CPP                        #-}
{-# LANGUAGE TypeFamilies               #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE FlexibleContexts           #-}
{-# LANGUAGE FlexibleInstances          #-}
{-# LANGUAGE ScopedTypeVariables        #-}
{-# LANGUAGE CPP                        #-}
{-# LANGUAGE UndecidableInstances       #-}
{-# LANGUAGE ExistentialQuantification  #-}
{-# LANGUAGE ConstraintKinds            #-}
module Raaz.Hash.Internal.HMAC
       ( HMAC (..)
         -- * Combinators for computing HMACs
       , hmac, hmacFile, hmacSource
         -- ** Computing HMACs using non-standard implementations.
       , hmac', hmacFile', hmacSource'
       ) where

import           Control.Applicative
import           Control.Monad.IO.Class    (liftIO)
import           Data.Bits                 (xor)
import           Data.ByteString.Char8     (ByteString)
import qualified Data.ByteString           as B
import qualified Data.ByteString.Lazy      as L
import           Data.Monoid
import           Data.String
import           Data.Word
import           Foreign.Ptr               ( castPtr      )
import           Foreign.Storable          ( Storable(..) )
import           Prelude                   hiding (length, replicate)
import           System.IO
import           System.IO.Unsafe     (unsafePerformIO)

import           Raaz.Core          hiding (alignment)
import           Raaz.Core.Parse.Applicative
import           Raaz.Core.Transfer
import           Raaz.Random

import           Raaz.Hash.Internal

--------------------------- The HMAC Key -----------------------------

-- | The HMAC key type. The HMAC keys are usually of size at most the
-- block size of the associated hash, although the hmac construction
-- allows using keys arbitrary size. Using keys of small size, in
-- particular smaller than the size of the corresponding hash, can can
-- compromise security.
--
-- == A note on `Show` and `IsString` instances of keys.
--
-- As any other cryptographic type HMAC keys also have a `IsString`
-- and `Show` instance which is essentially the key expressed in
-- base16.  Keys larger than the block size of the underlying hashes
-- are shortened by applying the appropriate hash. As a result the
-- `show` and `fromString` need not be inverses of each other.
--
newtype HMACKey h = HMACKey { unKey :: B.ByteString }
#if MIN_VERSION_base(4,11,0)
                 deriving (Semigroup, Monoid)
#else
                 deriving Monoid
#endif

instance (Hash h, Recommendation h) => Storable (HMACKey h) where

  sizeOf    _  = fromIntegral $ blockSize (undefined :: h)

  alignment _  = alignment (undefined :: Word8)

  peek         = unsafeRunParser (HMACKey <$> parseByteString (blockSize (undefined :: h))) . castPtr

  poke ptr key = unsafeWrite (writeByteString $ hmacAdjustKey key) $ castPtr ptr

hmacAdjustKey :: (Hash h, Recommendation h, Encodable h)
              => HMACKey h -- ^ the key.
              -> ByteString
hmacAdjustKey key = padIt trimedKey
  where keyStr      = unKey key
        trimedKey   = if length keyStr > sz
                      then toByteString
                           $ hash keyStr `asTypeOf` theHash key
                      else keyStr
        padIt k     = k <> replicate (sz - length k) 0
        sz          = blockSize $ theHash key
        theHash     :: HMACKey h -> h
        theHash  _  = undefined

-- The HMACKey is just stored as a binary data.
instance (Hash h, Recommendation h) => EndianStore (HMACKey h) where
  store            = poke
  load             = peek
  adjustEndian _ _ = return ()

instance (Hash h, Recommendation h) => RandomStorable (HMACKey h) where
  fillRandomElements = unsafeFillRandomElements

instance (Hash h, Recommendation h) => Encodable (HMACKey h)

-- | Base16 representation of the string.
instance IsString (HMACKey h) where
  fromString = HMACKey
               . (decodeFormat :: Base16 -> ByteString)
               . fromString

instance Show (HMACKey h) where
  show = show . (encodeByteString :: ByteString -> Base16) . unKey

----------------  The HMAC type -----------------------------------------

-- | The HMAC associated to a hash value. The HMAC type is essentially
-- the underlying hash type wrapped inside a newtype. Therefore, the
-- `Eq` instance for HMAC is essentially the `Eq` instance for the
-- underlying hash. It is safe against timing attack provided the
-- underlying hash comparison is safe under timing attack.
newtype HMAC h = HMAC {unHMAC :: h} deriving ( Eq, Storable
                                             , EndianStore
                                             , Encodable
                                             , IsString
                                             )
instance Show h => Show (HMAC h) where
  show  = show . unHMAC


type instance  Key (HMAC h) = HMACKey h

-- | Compute the hash of a pure byte source like, `B.ByteString`.
hmac :: ( Hash h, Recommendation h, PureByteSource src )
     => Key (HMAC h)
     -> src  -- ^ Message
     -> HMAC h
hmac key = unsafePerformIO . hmacSource key
{-# INLINEABLE hmac #-}
{-# SPECIALIZE hmac :: (Hash h, Recommendation h) => Key (HMAC h) -> B.ByteString -> HMAC h #-}
{-# SPECIALIZE hmac :: (Hash h, Recommendation h) => Key (HMAC h) -> L.ByteString -> HMAC h #-}

-- | Compute the hmac of file.
hmacFile :: (Hash h, Recommendation h)
         => Key (HMAC h) -- ^ Key to use for mac-ing
         -> FilePath     -- ^ File to be hashed
         -> IO (HMAC h)
hmacFile key fileName = withBinaryFile fileName ReadMode $ hmacSource key
{-# INLINEABLE hmacFile #-}

-- | Compute the hmac of a generic byte source.
hmacSource :: ( Hash h, Recommendation h, ByteSource src )
           => Key (HMAC h)  -- ^ key to use for mac-ing.
           -> src           -- ^ Message
           -> IO (HMAC h)
hmacSource = go undefined
  where go :: (Hash h, Recommendation h, ByteSource src)
              => h -> Key (HMAC h) -> src -> IO (HMAC h)
        go h = hmacSource' (recommended h)

{-# INLINEABLE hmacSource #-}
{-# SPECIALIZE hmacSource :: (Hash h, Recommendation h) => Key (HMAC h) -> Handle -> IO (HMAC h) #-}


-- | Compute the hmac of a pure byte source like, `B.ByteString`.
hmac' :: ( Hash h, Recommendation h, PureByteSource src )
      => Implementation h
      -> Key (HMAC h)
      -> src  -- ^ Message
      -> HMAC h
hmac' impl key = unsafePerformIO . hmacSource' impl key
{-# INLINEABLE hmac' #-}
{-# SPECIALIZE hmac' :: (Hash h, Recommendation h)
                     => Implementation h
                     -> Key (HMAC h)
                     -> B.ByteString
                     -> HMAC h
  #-}
{-# SPECIALIZE hmac' :: (Hash h, Recommendation h)
                     => Implementation h
                     -> Key (HMAC h)
                     -> L.ByteString
                     -> HMAC h
  #-}


-- | Compute the hmac of file.
hmacFile' :: (Hash h, Recommendation h)
         => Implementation h
         -> Key (HMAC h)
         -> FilePath  -- ^ File to be hashed
         -> IO (HMAC h)
hmacFile' impl key fileName = withBinaryFile fileName ReadMode $ hmacSource' impl key
{-# INLINEABLE hmacFile' #-}

-- | Compute the hmac of a generic ByteSource using a given implementation.
hmacSource' :: (Hash h, Recommendation h, ByteSource src)
            => Implementation h
            -> Key (HMAC h)
            -> src
            -> IO (HMAC h)
hmacSource' imp@(SomeHashI hI) key src =
  insecurely $ do

    -- Hash the first block for the inner hash
    initialise ()
    allocate $ \ ptr -> do
      liftIO $ unsafeCopyToPointer innerFirstBlock ptr
      compress hI ptr $ toEnum 1

    -- Finish it by hashing the source.
    innerHash <- completeHashing hI src


    -- Hash the outer block.
    initialise ()
    allocate $ \ ptr -> do
      liftIO $ unsafeCopyToPointer outerFirstBlock ptr
      compress hI ptr $ toEnum 1

    -- Finish it with hashing the  hash computed above
    HMAC <$> completeHashing hI (toByteString innerHash)

  where allocate = liftPointerAction $ allocBufferFor imp $ (toEnum 1) `asTypeOf` (theBlock key)
        innerFirstBlock = B.map (xor 0x36) $ hmacAdjustKey key
        outerFirstBlock = B.map (xor 0x5c) $ hmacAdjustKey key
        theBlock :: Key (HMAC h) -> BLOCKS h
        theBlock _ = toEnum 1
