-- |
-- Module      : Crypto.MAC
-- License     : BSD-style
-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
-- Stability   : experimental
-- Portability : unknown
--
-- Crypto hash generic MAC (Message Authentification Code) module
--
{-# LANGUAGE BangPatterns #-}
module Crypto.MAC
    (
    -- * MAC algorithms
      HMAC(..)
    , hmac
    , hmacAlg
    -- ** Incremental MAC algorithms
    , HMACContext
    , hmacInit
    , hmacInitAlg
    , hmacUpdate
    , hmacFinalize
    ) where

import Crypto.Hash
import Data.ByteString (ByteString)
import Data.Byteable
import Data.Bits (xor)
import qualified Data.ByteString as B

-- -------------------------------------------------------------------------- --
-- Incremental HMAC

-- | Represent an ongoing HMAC state, that can be appended with 'hmacUpdate'
-- and finalize to an HMAC with 'hmacFinalize'
data HMACContext hashalg = HMACContext !(Context hashalg) !(Context hashalg)

-- | Initialize a new incremental HMAC context
hmacInit :: HashAlgorithm a
         => ByteString -- ^ Secret key
         -> HMACContext a
hmacInit :: ByteString -> HMACContext a
hmacInit secret :: ByteString
secret = Context a -> Context a -> HMACContext a
forall hashalg.
Context hashalg -> Context hashalg -> HMACContext hashalg
HMACContext Context a
octx Context a
ictx
    where ctxInit :: Context a
ctxInit = Context a
forall a. HashAlgorithm a => Context a
hashInit
          ictx :: Context a
ictx = Context a -> [ByteString] -> Context a
forall a. HashAlgorithm a => Context a -> [ByteString] -> Context a
hashUpdates Context a
ctxInit [ByteString
ipad]
          octx :: Context a
octx = Context a -> [ByteString] -> Context a
forall a. HashAlgorithm a => Context a -> [ByteString] -> Context a
hashUpdates Context a
ctxInit [ByteString
opad]
          ipad :: ByteString
ipad = (Word8 -> Word8) -> ByteString -> ByteString
B.map (Word8 -> Word8 -> Word8
forall a. Bits a => a -> a -> a
xor 0x36) ByteString
k'
          opad :: ByteString
opad = (Word8 -> Word8) -> ByteString -> ByteString
B.map (Word8 -> Word8 -> Word8
forall a. Bits a => a -> a -> a
xor 0x5c) ByteString
k'

          k' :: ByteString
k'  = ByteString -> ByteString -> ByteString
B.append ByteString
kt ByteString
pad
          kt :: ByteString
kt  = if ByteString -> Int
B.length ByteString
secret Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
blockSize then Digest a -> ByteString
forall a. Byteable a => a -> ByteString
toBytes (ByteString -> Digest a
hashF ByteString
secret) else ByteString
secret
          pad :: ByteString
pad = Int -> Word8 -> ByteString
B.replicate (Int -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
blockSize Int -> Int -> Int
forall a. Num a => a -> a -> a
- ByteString -> Int
B.length ByteString
kt) 0
          hashF :: ByteString -> Digest a
hashF = Context a -> Digest a
forall a. HashAlgorithm a => Context a -> Digest a
hashFinalize (Context a -> Digest a)
-> (ByteString -> Context a) -> ByteString -> Digest a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Context a -> ByteString -> Context a
forall a. HashAlgorithm a => Context a -> ByteString -> Context a
hashUpdate Context a
ctxInit
          blockSize :: Int
blockSize = Context a -> Int
forall a. HashAlgorithm a => Context a -> Int
hashBlockSize Context a
ctxInit

-- | Initialize a new incremental HMAC context with a given hash algorithm.
hmacInitAlg :: HashAlgorithm a
            => a           -- ^ the hash algorithm the actual value is unused.
            -> ByteString  -- ^ Secret key
            -> HMACContext a
hmacInitAlg :: a -> ByteString -> HMACContext a
hmacInitAlg _ secret :: ByteString
secret = ByteString -> HMACContext a
forall a. HashAlgorithm a => ByteString -> HMACContext a
hmacInit ByteString
secret

-- | Incrementally update a HMAC context
hmacUpdate :: HashAlgorithm a
           => HMACContext a
           -> ByteString -- ^ Message to Mac
           -> HMACContext a
hmacUpdate :: HMACContext a -> ByteString -> HMACContext a
hmacUpdate (HMACContext octx :: Context a
octx ictx :: Context a
ictx) msg :: ByteString
msg =
    Context a -> Context a -> HMACContext a
forall hashalg.
Context hashalg -> Context hashalg -> HMACContext hashalg
HMACContext Context a
octx (Context a -> ByteString -> Context a
forall a. HashAlgorithm a => Context a -> ByteString -> Context a
hashUpdate Context a
ictx ByteString
msg)

-- | Finalize a HMAC context and return the HMAC.
hmacFinalize :: HashAlgorithm a
             => HMACContext a
             -> HMAC a
hmacFinalize :: HMACContext a -> HMAC a
hmacFinalize (HMACContext octx :: Context a
octx ictx :: Context a
ictx) =
    Digest a -> HMAC a
forall a. Digest a -> HMAC a
HMAC (Digest a -> HMAC a) -> Digest a -> HMAC a
forall a b. (a -> b) -> a -> b
$ Context a -> Digest a
forall a. HashAlgorithm a => Context a -> Digest a
hashFinalize (Context a -> Digest a) -> Context a -> Digest a
forall a b. (a -> b) -> a -> b
$ Context a -> [ByteString] -> Context a
forall a. HashAlgorithm a => Context a -> [ByteString] -> Context a
hashUpdates Context a
octx [Digest a -> ByteString
forall a. Byteable a => a -> ByteString
toBytes (Digest a -> ByteString) -> Digest a -> ByteString
forall a b. (a -> b) -> a -> b
$ Context a -> Digest a
forall a. HashAlgorithm a => Context a -> Digest a
hashFinalize Context a
ictx]