{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-}
module Crypto.Nettle.CCM
( ccmInit
, ccmInitTLS
) where
import Crypto.Cipher.Types
import qualified Data.ByteString as B
import Data.Byteable
import Nettle.Utils
{-# ANN module "HLint: ignore Use camelCase" #-}
data CCM cipher
= CCM_Header (Int, Int, B.ByteString) B.ByteString
| CCM_Enc (Int, Int, B.ByteString) B.ByteString (IV cipher) B.ByteString
| CCM_Dec (Int, Int, B.ByteString) B.ByteString (IV cipher) B.ByteString
ccmInit
:: (BlockCipher cipher, Byteable iv)
=> Int
-> Int
-> cipher
-> iv
-> Maybe (AEAD cipher)
ccmInit t q cipher nonce = ccm_init t q cipher nonce >>= Just . AEAD cipher . AEADState
ccm_init :: (BlockCipher cipher, Byteable iv) => Int -> Int -> cipher -> iv -> Maybe (CCM cipher)
ccm_init t q cipher nonce = if valid then Just $ CCM_Header (t, q, toBytes nonce) B.empty else Nothing
where
valid = valid_cipher && valid_t && valid_q && valid_nonce
valid_cipher = blockSize cipher == 16
valid_t = t >= 4 && t <= 16 && even t
valid_q = q >= 2 && q <= 8
nonce_len = 15 - q
valid_nonce = byteableLength nonce == fromIntegral nonce_len
ccmInitTLS
:: (BlockCipher cipher, Byteable iv)
=> cipher
-> iv
-> Maybe (AEAD cipher)
ccmInitTLS = ccmInit 16 3
ccm_encodeAdditionalLength :: B.ByteString -> B.ByteString
ccm_encodeAdditionalLength s = B.append (encLen $ B.length s) s where
encLen n
| n == 0 = B.empty
| n < (2^(16::Int)-2^(8::Int)) = B.pack $ netEncode 2 n
| n < (2^(32::Int)) = B.pack (0xff:0xfe:netEncode 4 n)
| otherwise = B.pack (0xff:0xff:netEncode 8 n)
pad_zero :: Int -> B.ByteString -> B.ByteString
pad_zero l s = B.append s $ B.replicate (l - 1 - (B.length s - 1) `mod` l) 0
_makeIV :: BlockCipher cipher => B.ByteString -> IV cipher
_makeIV iv = let Just iv' = makeIV iv in iv'
ccm_start_iv :: BlockCipher cipher => (Int, Int, B.ByteString) -> IV cipher
ccm_start_iv (_, q, nonce) = _makeIV $ B.concat [B.singleton $ fromIntegral $ q - 1, nonce, B.replicate (q - 1) 0, B.singleton 1]
ccm_tag_iv :: BlockCipher cipher => (Int, Int, B.ByteString) -> IV cipher
ccm_tag_iv (_, q, nonce) = _makeIV $ B.concat [B.singleton $ fromIntegral $ q - 1, nonce, B.replicate q 0]
ccm_crypt :: BlockCipher cipher => cipher -> IV cipher -> B.ByteString -> (B.ByteString, IV cipher)
ccm_crypt key iv src = let
blocks = (B.length src + 15) `div` 16
dst = ctrCombine key iv src
iv' = ivAdd iv blocks
in (dst, iv')
ccm_tag :: BlockCipher cipher => cipher -> (Int, Int, B.ByteString) -> B.ByteString -> B.ByteString -> Int -> AuthTag
ccm_tag key (t, q, nonce) header msg taglen = let
auth_flags = (if B.length header > 0 then 64 else 0) + 4*(fromIntegral t - 2) + (fromIntegral q - 1)
b0 = B.concat [B.singleton auth_flags, nonce, B.pack $ netEncode q $ B.length msg]
blocks = B.concat [b0, pad_zero 16 $ ccm_encodeAdditionalLength header, pad_zero 16 msg]
tag = fst $ ccm_crypt key (ccm_tag_iv (t, q, nonce)) $ B.drop (B.length blocks - 16) $ cbcEncrypt key nullIV blocks
in AuthTag $ B.take taglen tag
instance BlockCipher cipher => AEADModeImpl cipher (CCM cipher) where
aeadStateAppendHeader _ (CCM_Header (t, q, nonce) header) src = CCM_Header (t, q, nonce) $ B.append header src
aeadStateAppendHeader _ _ _ = error "can't aeadStateAppendHeader anymore, already have real data"
aeadStateEncrypt key (CCM_Header (t, q, nonce) header) src = aeadStateEncrypt key (CCM_Enc (t, q, nonce) header iv B.empty) src
where iv = ccm_start_iv (t, q, nonce)
aeadStateEncrypt key (CCM_Enc (t, q, nonce) header iv msg) src = let
(dst, iv') = ccm_crypt key iv src
in (dst, CCM_Enc (t, q, nonce) header iv' $ B.append msg src)
aeadStateEncrypt _ _ _ = error "can't aeadStateEncrypt anymore, already is in decrypt mode"
aeadStateDecrypt key (CCM_Header (t, q, nonce) header) src = aeadStateDecrypt key (CCM_Dec (t, q, nonce) header iv B.empty) src
where iv = ccm_start_iv (t, q, nonce)
aeadStateDecrypt key (CCM_Dec (t, q, nonce) header iv msg) src = let
(dst, iv') = ccm_crypt key iv src
in (dst, CCM_Enc (t, q, nonce) header iv' $ B.append msg dst)
aeadStateDecrypt _ _ _ = error "can't aeadStateDecrypt anymore, already is in encrypt mode"
aeadStateFinalize key (CCM_Header (t, q, nonce) header ) taglen = ccm_tag key (t, q, nonce) header B.empty taglen
aeadStateFinalize key (CCM_Enc (t, q, nonce) header _ msg) taglen = ccm_tag key (t, q, nonce) header msg taglen
aeadStateFinalize key (CCM_Dec (t, q, nonce) header _ msg) taglen = ccm_tag key (t, q, nonce) header msg taglen