Z-Botan-0.3.0.0: Z/Crypto/MAC.hs
{-|
Module : Z.Crypto.MAC
Description : Message Authentication Codes (MAC)
Copyright : YouShi, Dong Han, 2021
License : BSD
Maintainer : winterland1989@gmail.com
Stability : experimental
Portability : non-portable
A Message Authentication Code algorithm computes a tag over a message utilizing a shared secret key. Thus a valid tag confirms the authenticity and integrity of the message. Only entities in possession of the shared secret key are able to verify the tag.
-}
module Z.Crypto.MAC (
-- * MAC type
MACType(..)
, MAC, macName, macSize
-- * IUF interface
, newMAC
, setKeyMAC
, updateMAC
, finalMAC
, clearMAC
-- * function interface
, mac, macChunks
-- * BIO interface
, sinkToMAC
-- * Internal helper
, macTypeToCBytes
, withMAC
) where
import GHC.Generics
import System.IO.Unsafe (unsafePerformIO)
import Z.Botan.Exception
import Z.Botan.FFI
import Z.Crypto.Cipher (BlockCipherType, blockCipherTypeToCBytes)
import Z.Crypto.Hash (HashType, hashTypeToCBytes)
import Z.Data.CBytes as CB
import Z.Data.JSON (JSON)
import qualified Z.Data.Text as T
import qualified Z.Data.Vector as V
import Z.Foreign
import Z.IO.BIO
data MACType = CMAC BlockCipherType
-- ^ A modern CBC-MAC variant that avoids the security problems of plain CBC-MAC.
-- Approved by NIST. Also sometimes called OMAC.
| GMAC BlockCipherType
-- ^ GMAC is related to the GCM authenticated cipher mode.
-- It is quite slow unless hardware support for carryless multiplications is available.
-- A new nonce must be used with each message authenticated, or otherwise all security is lost.
| CBC_MAC BlockCipherType
-- ^ An older authentication code based on a block cipher.
-- Serious security problems,
-- in particular insecure if messages of several different lengths are authenticated.
-- Avoid unless required for compatibility.
| HMAC HashType
-- ^ A message authentication code based on a hash function. Very commonly used.
| Poly1305
-- ^ A polynomial mac (similar to GMAC). Very fast, but tricky to use safely.
-- Forms part of the ChaCha20Poly1305 AEAD mode.
-- A new key must be used for each message, or all security is lost.
| SipHash Int Int
-- ^ A modern and very fast PRF. Produces only a 64-bit output.
-- Defaults to “SipHash(2,4)” which is the recommended configuration,
-- using 2 rounds for each input block and 4 rounds for finalization.
| X9'19_MAC
-- ^ A CBC-MAC variant sometimes used in finance. Always uses DES.
-- Sometimes called the “DES retail MAC”, also standardized in ISO 9797-1.
-- It is slow and has known attacks. Avoid unless required.
deriving (Show, Read, Eq, Ord, Generic)
deriving anyclass (T.Print, JSON)
macTypeToCBytes :: MACType -> CBytes
macTypeToCBytes (CMAC bc ) = CB.concat ["CMAC(", blockCipherTypeToCBytes bc, ")"]
macTypeToCBytes (GMAC bc ) = CB.concat ["GMAC(", blockCipherTypeToCBytes bc, ")"]
macTypeToCBytes (CBC_MAC bc) = CB.concat ["CBC-MAC(", blockCipherTypeToCBytes bc, ")"]
macTypeToCBytes (HMAC ht) = CB.concat ["HMAC(", hashTypeToCBytes ht, ")"]
macTypeToCBytes Poly1305 = "Poly1305"
macTypeToCBytes (SipHash r1 r2) = CB.concat ["SipHash(", sizeCBytes r1, ",", sizeCBytes r2, ")"]
where
sizeCBytes = CB.fromText . T.toText
macTypeToCBytes X9'19_MAC = "X9.19-MAC"
data MAC = MAC
{ macStruct :: {-# UNPACK #-} !BotanStruct
, macName :: {-# UNPACK #-} !CBytes -- ^ mac algo name
, macSize :: {-# UNPACK #-} !Int -- ^ mac output size in bytes
}
deriving (Show, Generic)
deriving anyclass T.Print
-- | Pass MAC to FFI as 'botan_mac_t'.
withMAC :: MAC -> (BotanStructT -> IO r) -> IO r
{-# INLINABLE withMAC #-}
withMAC (MAC m _ _) = withBotanStruct m
-- | Create a new 'MAC' object.
newMAC :: HasCallStack => MACType -> IO MAC
{-# INLINABLE newMAC #-}
newMAC typ = do
let name = macTypeToCBytes typ
bs <- newBotanStruct
(\ bts -> withCBytesUnsafe name $ \ pt ->
(botan_mac_init bts pt 0))
botan_mac_destroy
(osiz, _) <- withBotanStruct bs $ \ pbs ->
allocPrimUnsafe @CSize $ \ pl ->
botan_mac_output_length pbs pl
return (MAC bs name (fromIntegral osiz))
-- | Set the random key.
setKeyMAC :: HasCallStack => MAC -> V.Bytes -> IO ()
{-# INLINABLE setKeyMAC #-}
setKeyMAC (MAC bts _ _) bs =
withBotanStruct bts $ \pbts->
withPrimVectorUnsafe bs $ \pbs off len ->
throwBotanIfMinus_ (hs_botan_mac_set_key pbts pbs off len)
-- | Feed a chunk of input into a 'MAC' object.
updateMAC :: HasCallStack => MAC -> V.Bytes -> IO ()
{-# INLINABLE updateMAC #-}
updateMAC (MAC bts _ _) bs =
withBotanStruct bts $ \ pbts ->
withPrimVectorUnsafe bs $ \ pbs off len ->
throwBotanIfMinus_ (hs_botan_mac_update pbts pbs off len)
finalMAC :: HasCallStack => MAC -> IO V.Bytes
{-# INLINABLE finalMAC #-}
finalMAC (MAC bts _ siz) =
withBotanStruct bts $ \ pbts -> do
fst <$> allocPrimVectorUnsafe siz (\ pout ->
throwBotanIfMinus_ (botan_mac_final pbts pout))
-- | Reset the state of MAC object back to clean, as if no input has been supplied.
clearMAC :: HasCallStack => MAC -> IO ()
{-# INLINABLE clearMAC #-}
clearMAC (MAC bts _ _) =
throwBotanIfMinus_ (withBotanStruct bts hs_botan_mac_clear)
-- | Trun 'MAC' to a 'V.Bytes' sink, update 'MAC' by write bytes to the sink.
--
sinkToMAC :: HasCallStack => MAC -> Sink V.Bytes
{-# INLINABLE sinkToMAC #-}
sinkToMAC h = \ k mbs -> case mbs of
Just bs -> updateMAC h bs
_ -> k EOF
-- | Directly compute a message's mac
mac :: HasCallStack => MACType
-> V.Bytes -- ^ key
-> V.Bytes -- ^ input
-> V.Bytes
{-# INLINABLE mac #-}
mac mt key inp = unsafePerformIO $ do
m <- newMAC mt
setKeyMAC m key
updateMAC m inp
finalMAC m
-- | Directly compute a chunked message's mac.
macChunks :: HasCallStack => MACType -> V.Bytes -> [V.Bytes] -> V.Bytes
{-# INLINABLE macChunks #-}
macChunks mt key inps = unsafePerformIO $ do
m <- newMAC mt
setKeyMAC m key
mapM_ (updateMAC m) inps
finalMAC m