{-# OPTIONS_HADDOCK hide #-}
{-# LANGUAGE ExistentialQuantification #-}
-- |
-- Module      : Network.TLS.Cipher
-- License     : BSD-style
-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
-- Stability   : experimental
-- Portability : unknown
--
module Network.TLS.Cipher
    ( CipherKeyExchangeType(..)
    , Bulk(..)
    , BulkFunctions(..)
    , BulkDirection(..)
    , BulkState(..)
    , BulkStream(..)
    , BulkBlock
    , BulkAEAD
    , bulkInit
    , Hash(..)
    , Cipher(..)
    , CipherID
    , cipherKeyBlockSize
    , BulkKey
    , BulkIV
    , BulkNonce
    , BulkAdditionalData
    , cipherAllowedForVersion
    , cipherExchangeNeedMoreData
    , hasMAC
    , hasRecordIV
    ) where

import Crypto.Cipher.Types (AuthTag)
import Network.TLS.Types (CipherID)
import Network.TLS.Struct (Version(..))
import Network.TLS.Crypto (Hash(..), hashDigestSize)

import qualified Data.ByteString as B

-- FIXME convert to newtype
type BulkKey = B.ByteString
type BulkIV = B.ByteString
type BulkNonce = B.ByteString
type BulkAdditionalData = B.ByteString

data BulkState =
      BulkStateStream BulkStream
    | BulkStateBlock  BulkBlock
    | BulkStateAEAD   BulkAEAD
    | BulkStateUninitialized

instance Show BulkState where
    show (BulkStateStream _)      = "BulkStateStream"
    show (BulkStateBlock _)       = "BulkStateBlock"
    show (BulkStateAEAD _)        = "BulkStateAEAD"
    show (BulkStateUninitialized) = "BulkStateUninitialized"

newtype BulkStream = BulkStream (B.ByteString -> (B.ByteString, BulkStream))

type BulkBlock = BulkIV -> B.ByteString -> (B.ByteString, BulkIV)

type BulkAEAD = BulkNonce -> B.ByteString -> BulkAdditionalData -> (B.ByteString, AuthTag)

data BulkDirection = BulkEncrypt | BulkDecrypt
    deriving (Show,Eq)

bulkInit :: Bulk -> BulkDirection -> BulkKey -> BulkState
bulkInit bulk direction key =
    case bulkF bulk of
        BulkBlockF  ini -> BulkStateBlock  (ini direction key)
        BulkStreamF ini -> BulkStateStream (ini direction key)
        BulkAeadF   ini -> BulkStateAEAD   (ini direction key)

data BulkFunctions =
      BulkBlockF  (BulkDirection -> BulkKey -> BulkBlock)
    | BulkStreamF (BulkDirection -> BulkKey -> BulkStream)
    | BulkAeadF   (BulkDirection -> BulkKey -> BulkAEAD)

hasMAC,hasRecordIV :: BulkFunctions -> Bool

hasMAC (BulkBlockF _ ) = True
hasMAC (BulkStreamF _) = True
hasMAC (BulkAeadF _  ) = False

hasRecordIV = hasMAC

data CipherKeyExchangeType =
      CipherKeyExchange_RSA
    | CipherKeyExchange_DH_Anon
    | CipherKeyExchange_DHE_RSA
    | CipherKeyExchange_ECDHE_RSA
    | CipherKeyExchange_DHE_DSS
    | CipherKeyExchange_DH_DSS
    | CipherKeyExchange_DH_RSA
    | CipherKeyExchange_ECDH_ECDSA
    | CipherKeyExchange_ECDH_RSA
    | CipherKeyExchange_ECDHE_ECDSA
    deriving (Show,Eq)

data Bulk = Bulk
    { bulkName         :: String
    , bulkKeySize      :: Int
    , bulkIVSize       :: Int
    , bulkBlockSize    :: Int
    , bulkF            :: BulkFunctions
    }

instance Show Bulk where
    show bulk = bulkName bulk
instance Eq Bulk where
    b1 == b2 = and [ bulkName b1 == bulkName b2
                   , bulkKeySize b1 == bulkKeySize b2
                   , bulkIVSize b1 == bulkIVSize b2
                   , bulkBlockSize b1 == bulkBlockSize b2
                   ]

-- | Cipher algorithm
data Cipher = Cipher
    { cipherID           :: CipherID
    , cipherName         :: String
    , cipherHash         :: Hash
    , cipherBulk         :: Bulk
    , cipherKeyExchange  :: CipherKeyExchangeType
    , cipherMinVer       :: Maybe Version
    }

cipherKeyBlockSize :: Cipher -> Int
cipherKeyBlockSize cipher = 2 * (hashDigestSize (cipherHash cipher) + bulkIVSize bulk + bulkKeySize bulk)
  where bulk = cipherBulk cipher

-- | Check if a specific 'Cipher' is allowed to be used
-- with the version specified
cipherAllowedForVersion :: Version -> Cipher -> Bool
cipherAllowedForVersion ver cipher =
    case cipherMinVer cipher of
        Nothing   -> True
        Just cVer -> cVer <= ver

instance Show Cipher where
    show c = cipherName c

instance Eq Cipher where
    (==) c1 c2 = cipherID c1 == cipherID c2

cipherExchangeNeedMoreData :: CipherKeyExchangeType -> Bool
cipherExchangeNeedMoreData CipherKeyExchange_RSA         = False
cipherExchangeNeedMoreData CipherKeyExchange_DH_Anon     = True
cipherExchangeNeedMoreData CipherKeyExchange_DHE_RSA     = True
cipherExchangeNeedMoreData CipherKeyExchange_ECDHE_RSA   = True
cipherExchangeNeedMoreData CipherKeyExchange_DHE_DSS     = True
cipherExchangeNeedMoreData CipherKeyExchange_DH_DSS      = False
cipherExchangeNeedMoreData CipherKeyExchange_DH_RSA      = False
cipherExchangeNeedMoreData CipherKeyExchange_ECDH_ECDSA  = True
cipherExchangeNeedMoreData CipherKeyExchange_ECDH_RSA    = True
cipherExchangeNeedMoreData CipherKeyExchange_ECDHE_ECDSA = True