packages feed

nettle-0.4: src/Crypto/Nettle/Ciphers/Internal.hs

{-# OPTIONS_HADDOCK hide #-}
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FlexibleContexts, LambdaCase #-}

module Crypto.Nettle.Ciphers.Internal
	( NettleCipher(..)
	, NettleBlockCipher(..)
	, NettleStreamCipher(..)
	, NettleBlockedStreamCipher(..)
	, NettleAeadModeImpl(..)
	, NettleGCM
	, nettle_cipherInit
	, nettle_cipherInit'
	, nettle_ecbEncrypt
	, nettle_ecbDecrypt
	, nettle_cbcEncrypt
	, nettle_cbcDecrypt
	, nettle_cfbEncrypt
	, nettle_cfbDecrypt
	, nettle_ctrCombine
	, nettle_streamCombine
	, nettle_streamSetNonce
	, nettle_blockedStreamCombine
	, nettle_blockedStreamSetNonce
	, nettle_gcm_aeadInit
	, nettle_gcm_aeadStateAppendHeader
	, nettle_gcm_aeadStateEncrypt
	, nettle_gcm_aeadStateDecrypt
	, nettle_gcm_aeadStateFinalize
	) where

import Crypto.Cipher.Types as CCT
import Crypto.Error

import Data.Tagged
import qualified Data.ByteArray as BA
import qualified Data.ByteString as B

import Foreign.Ptr (alignPtr, ptrToWordPtr)
import Foreign.Marshal.Utils (copyBytes)

import Nettle.Utils
import Crypto.Nettle.Ciphers.ForeignImports

-- internal functions are not camelCase on purpose
{-# ANN module "HLint: ignore Use camelCase" #-}

class NettleCipher c where
	-- | pointer to new context, key length, (const) key pointer
	nc_cipherInit    :: Tagged c (Ptr Word8 -> Word -> Ptr Word8 -> IO())
	nc_cipherName    :: Tagged c String
	nc_cipherKeySize :: Tagged c CCT.KeySizeSpecifier
	nc_ctx_size      :: Tagged c Int
	nc_ctx           :: c -> BA.ScrubbedBytes
	nc_Ctx           :: BA.ScrubbedBytes -> c
class NettleCipher c => NettleBlockCipher c where
	nbc_blockSize          :: Tagged c Int
	nbc_encrypt_ctx_offset :: Tagged c (Ptr Word8 -> Ptr Word8)
	nbc_encrypt_ctx_offset = Tagged id
	nbc_decrypt_ctx_offset :: Tagged c (Ptr Word8 -> Ptr Word8)
	nbc_decrypt_ctx_offset = Tagged id
	nbc_ecb_encrypt        :: Tagged c NettleCryptFunc
	nbc_ecb_decrypt        :: Tagged c NettleCryptFunc
	nbc_fun_encrypt        :: Tagged c (FunPtr NettleCryptFunc)
	nbc_fun_decrypt        :: Tagged c (FunPtr NettleCryptFunc)
class NettleCipher c => NettleStreamCipher c where
	nsc_streamCombine      :: Tagged c NettleCryptFunc
	nsc_nonceSize          :: Tagged c CCT.KeySizeSpecifier
	nsc_nonceSize          = Tagged $ CCT.KeySizeEnum []
	nsc_setNonce           :: Tagged c (Maybe (Ptr Word8 -> Word -> Ptr Word8 -> IO ()))
	nsc_setNonce           = Tagged Nothing

-- stream cipher based on generating (large) blocks to XOR with input,
-- but don't keep incomplete blocks in the state, so we have to do that here
class NettleCipher c => NettleBlockedStreamCipher c where
	nbsc_blockSize          :: Tagged c Int
	-- set new incomplete state
	nbsc_IncompleteState    :: c -> B.ByteString -> c
	nbsc_incompleteState    :: c -> B.ByteString
	nbsc_streamCombine      :: Tagged c NettleCryptFunc
	nbsc_nonceSize          :: Tagged c CCT.KeySizeSpecifier
	nbsc_nonceSize          = Tagged $ CCT.KeySizeEnum []
	nbsc_setNonce           :: Tagged c (Maybe (Ptr Word8 -> Word -> Ptr Word8 -> IO ()))
	nbsc_setNonce           = Tagged Nothing

class NettleAeadModeImpl c state where
	nettle_aead_mode_impl :: c -> CCT.AEADModeImpl state

nettle_cipherInit :: (NettleCipher c, BA.ByteArray key) => key -> CryptoFailable c
nettle_cipherInit k = let ctx = nettle_cipherInit' (nc_cipherInit `witness` (throwCryptoError ctx)) k in ctx

nettle_cipherInit' :: (NettleCipher c, BA.ByteArray key) => (Ptr Word8 -> Word -> Ptr Word8 -> IO()) -> key -> CryptoFailable c
nettle_cipherInit' f k = let ctx = (\case {
		  Nothing -> CryptoPassed $ nc_Ctx $ key_init f (nc_ctx_size `witness` (throwCryptoError ctx)) k
		; Just e -> CryptoFailed e
		}) (validate_keySize (nc_cipherKeySize `witness` (throwCryptoError ctx)) k)
	in ctx

validate_keySize :: (BA.ByteArrayAccess key) => CCT.KeySizeSpecifier -> key -> Maybe CryptoError
validate_keySize spec k = case spec of
	CCT.KeySizeRange bot top -> if bot <= BA.length k && BA.length k <= top	then Nothing else Just CryptoError_KeySizeInvalid
	CCT.KeySizeEnum list     -> if (BA.length k) `elem` list		then Nothing else Just CryptoError_KeySizeInvalid
	CCT.KeySizeFixed f       -> if BA.length k == f				then Nothing else Just CryptoError_KeySizeInvalid

assert_blockSize :: (NettleBlockCipher c, BA.ByteArrayAccess ba) => c -> ba -> a -> a
assert_blockSize c src result = if 0 /= BA.length src `mod` (nbc_blockSize `witness` c) then error "input not a multiple of blockSize" else result

nettle_ecbEncrypt :: (CCT.BlockCipher c, NettleBlockCipher c, BA.ByteArrayAccess bin, BA.ByteArray bout) => c -> bin -> bout
nettle_ecbEncrypt c    src = assert_blockSize c src $ c_run_crypt   (nbc_encrypt_ctx_offset `witness` c)               (nbc_ecb_encrypt `witness` c) (nc_ctx c) src
nettle_ecbDecrypt :: (CCT.BlockCipher c, NettleBlockCipher c, BA.ByteArrayAccess bin, BA.ByteArray bout) => c -> bin -> bout
nettle_ecbDecrypt c    src = assert_blockSize c src $ c_run_crypt   (nbc_decrypt_ctx_offset `witness` c)               (nbc_ecb_decrypt `witness` c) (nc_ctx c) src
nettle_cbcEncrypt :: (CCT.BlockCipher c, NettleBlockCipher c, BA.ByteArrayAccess bin, BA.ByteArray bout) => c -> IV c -> bin -> bout
nettle_cbcEncrypt c iv src = assert_blockSize c src $ blockmode_run (nbc_encrypt_ctx_offset `witness` c) c_cbc_encrypt (nbc_fun_encrypt `witness` c) (nc_ctx c) iv src
nettle_cbcDecrypt :: (CCT.BlockCipher c, NettleBlockCipher c, BA.ByteArrayAccess bin, BA.ByteArray bout) => c -> IV c -> bin -> bout
nettle_cbcDecrypt c iv src = assert_blockSize c src $ blockmode_run (nbc_decrypt_ctx_offset `witness` c) c_cbc_decrypt (nbc_fun_decrypt `witness` c) (nc_ctx c) iv src
nettle_cfbEncrypt :: (CCT.BlockCipher c, NettleBlockCipher c, BA.ByteArrayAccess bin, BA.ByteArray bout) => c -> IV c -> bin -> bout
nettle_cfbEncrypt c iv src = assert_blockSize c src $ blockmode_run (nbc_encrypt_ctx_offset `witness` c) c_cfb_encrypt (nbc_fun_encrypt `witness` c) (nc_ctx c) iv src
nettle_cfbDecrypt :: (CCT.BlockCipher c, NettleBlockCipher c, BA.ByteArrayAccess bin, BA.ByteArray bout) => c -> IV c -> bin -> bout
nettle_cfbDecrypt c iv src = assert_blockSize c src $ blockmode_run (nbc_encrypt_ctx_offset `witness` c) c_cfb_decrypt (nbc_fun_encrypt `witness` c) (nc_ctx c) iv src
nettle_ctrCombine :: (CCT.BlockCipher c, NettleBlockCipher c, BA.ByteArrayAccess bin, BA.ByteArray bout) => c -> IV c -> bin -> bout
nettle_ctrCombine c        =                          blockmode_run (nbc_encrypt_ctx_offset `witness` c) c_ctr_crypt   (nbc_fun_encrypt `witness` c) (nc_ctx c)

nettle_streamCombine :: (NettleStreamCipher c, BA.ByteArrayAccess bin, BA.ByteArray bout) => c -> bin -> (bout, c)
nettle_streamCombine c indata = let (r, c') = stream_crypt (nsc_streamCombine `witness` c) (nc_ctx c) indata in (r, nc_Ctx c')
nettle_streamSetNonce :: NettleStreamCipher c => c -> B.ByteString -> Maybe c
nettle_streamSetNonce c nonce = case nsc_setNonce `witness` c of
	Nothing -> Nothing
	Just setnonce -> let ctx' = copyScrubbedBytes (nc_ctx c) in
		unsafeDupablePerformIO $
		BA.withByteArray ctx' $ \ctxptr ->
		withByteStringPtr nonce $ \noncelen nonceptr ->
		setnonce ctxptr noncelen nonceptr >>
		return (Just $ nc_Ctx ctx')

nettle_blockedStreamCombine :: (NettleBlockedStreamCipher c, BA.ByteArray bin, BA.ByteArray bout) => c -> bin -> (bout, c)
nettle_blockedStreamCombine c indata = if BA.length indata == 0 then (BA.convert indata, c) else
	let inc = nbsc_incompleteState c; blocksiz = nbsc_blockSize `witness` c in
	if B.length inc /= 0
		then let
			-- first xor remaining block, then combine the rest
			(i1, i2) = BA.splitAt (B.length inc) indata
			(inc1, inc2) = B.splitAt (BA.length indata) inc
			r1 = BA.xor i1 inc1
			c' = if B.length inc2 == 0 then nc_Ctx $ nc_ctx c else nbsc_IncompleteState c inc2
			(r, c'') = nettle_blockedStreamCombine c' i2
			in (BA.append r1 r, c'')
		else if BA.length indata `mod` blocksiz /= 0
			then let
				padding = BA.replicate (blocksiz - (BA.length indata `mod` blocksiz)) 0
				(r', c') = stream_crypt (nbsc_streamCombine `witness` c) (nc_ctx c) (BA.append indata padding)
				(r, inc') = BA.splitAt (BA.length indata) r'
				in (r, nbsc_IncompleteState (nc_Ctx c') (BA.convert inc'))
			else
				let (r, c') = stream_crypt (nbsc_streamCombine `witness` c) (nc_ctx c) indata in (r, nc_Ctx c')
nettle_blockedStreamSetNonce :: NettleBlockedStreamCipher c => c -> B.ByteString -> Maybe c
nettle_blockedStreamSetNonce c nonce = case nbsc_setNonce `witness` c of
	Nothing -> Nothing
	Just setnonce -> let ctx' = copyScrubbedBytes (nc_ctx c) in
		unsafeDupablePerformIO $
		BA.withByteArray ctx' $ \ctxptr ->
		withByteStringPtr nonce $ \noncelen nonceptr ->
		setnonce ctxptr noncelen nonceptr >>
		return (Just $ nc_Ctx ctx')


nettle_gcm_aeadInit              :: (NettleBlockCipher c, NettleAeadModeImpl c NettleGCM, BA.ByteArrayAccess iv) => c -> iv -> CryptoFailable (CCT.AEAD c)
nettle_gcm_aeadInit          c  iv = if nbc_blockSize `witness` c == 16 then CryptoPassed $ CCT.AEAD {CCT.aeadModeImpl = nettle_aead_mode_impl c, CCT.aeadState = gcm_init (nbc_encrypt_ctx_offset `witness` c) (nbc_fun_encrypt `witness` c) (nc_ctx c) iv} else CryptoFailed CryptoError_AEADModeNotSupported
nettle_gcm_aeadStateAppendHeader :: BA.ByteArrayAccess ba => t -> NettleGCM -> ba -> NettleGCM
nettle_gcm_aeadStateAppendHeader _ = gcm_update
nettle_gcm_aeadStateEncrypt      :: (NettleBlockCipher c, BA.ByteArrayAccess bin, BA.ByteArray bout) => c -> NettleGCM -> bin -> (bout, NettleGCM)
nettle_gcm_aeadStateEncrypt      c = gcm_crypt c_gcm_encrypt (nbc_encrypt_ctx_offset `witness` c) (nbc_fun_encrypt `witness` c) (nc_ctx c)
nettle_gcm_aeadStateDecrypt      :: (NettleBlockCipher c, BA.ByteArrayAccess bin, BA.ByteArray bout) => c -> NettleGCM -> bin -> (bout, NettleGCM)
nettle_gcm_aeadStateDecrypt      c = gcm_crypt c_gcm_decrypt (nbc_encrypt_ctx_offset `witness` c) (nbc_fun_encrypt `witness` c) (nc_ctx c)
nettle_gcm_aeadStateFinalize     :: NettleBlockCipher c => c -> NettleGCM -> Int -> CCT.AuthTag
nettle_gcm_aeadStateFinalize     c = gcm_digest              (nbc_encrypt_ctx_offset `witness` c) (nbc_fun_encrypt `witness` c) (nc_ctx c)



key_init
	:: BA.ByteArrayAccess k
	=> (Ptr Word8 -> Word -> Ptr Word8 -> IO ())
	-> Int -> k -> BA.ScrubbedBytes
key_init initfun size k = BA.unsafeCreate size $ \ctxptr ->
	BA.withByteArray k $ \kptr -> initfun ctxptr (fromIntegral $ BA.length k) kptr

-- run encryption/decryption with same length for in and output
c_run_crypt
	:: (BA.ByteArrayAccess bin,
	    BA.ByteArray bout)
	=> (Ptr Word8 -> Ptr Word8)
	-> NettleCryptFunc
	-> BA.ScrubbedBytes -> bin -> bout
c_run_crypt ctxoffset cfun ctx indata = unsafeDupablePerformIO $ BA.withByteArray ctx $ \ctxptr ->
	BA.withByteArray indata $ \indataptr ->
	BA.create (BA.length indata) $ \outptr ->
	cfun (ctxoffset ctxptr) (fromIntegral $ BA.length indata) outptr indataptr

blockmode_run
	:: (BA.ByteArrayAccess iv,
	    BA.ByteArrayAccess bin,
	    BA.ByteArray bout)
	=> (Ptr Word8 -> Ptr Word8)
	-> NettleBlockMode
	-> FunPtr NettleCryptFunc
	-> BA.ScrubbedBytes -> iv -> bin -> bout
blockmode_run ctxoffset mode crypt ctx iv indata = let iv' = copyAndConvertToScrubbedBytes iv in -- copy IV, may get modified
	unsafeDupablePerformIO $ BA.withByteArray ctx $ \ctxptr ->
	BA.withByteArray indata $ \indataptr ->
	BA.withByteArray iv' $ \ivptr ->
	BA.create (BA.length indata) $ \outptr ->
	mode (ctxoffset ctxptr) crypt (fromIntegral $ BA.length iv') ivptr (fromIntegral $ BA.length indata) outptr indataptr

data NettleGCM = NettleGCM !Int !BA.ScrubbedBytes !Int !BA.ScrubbedBytes

-- Nettle 4's GCM implementation uses 16-byte aligned accesses on its contexts,
-- while @ram@'s 'ScrubbedBytes' only guarantees 8-byte alignment.  The GCM
-- context buffers are therefore over-allocated with padding, and the pointer
-- handed to C is aligned to a 16-byte boundary.  The byte offset to the aligned
-- start of a buffer is stored alongside it; because 'copyScrubbedBytes' would
-- not preserve the alignment of the copied data, copies re-place the context
-- struct into the aligned region of a freshly allocated buffer.
gcm_alignment :: Int
gcm_alignment = 16

gcm_alignedSize :: Int -> Int
gcm_alignedSize n = n + gcm_alignment - 1

gcm_alignedOffset :: BA.ScrubbedBytes -> Int
gcm_alignedOffset ba = unsafeDupablePerformIO $
	BA.withByteArray ba $ \p ->
		return (fromIntegral ((fromIntegral gcm_alignment - ptrToWordPtr p `mod` fromIntegral gcm_alignment) `mod` fromIntegral gcm_alignment))

-- | Copy the @size@-byte struct from @src@ (located at @srcOff@) into the
--   aligned region of a fresh zeroed buffer.
gcm_copyAligned
	:: Int
	-> BA.ScrubbedBytes
	-> Int
	-> IO (BA.ScrubbedBytes, Int)
gcm_copyAligned size src srcOff = do
	dst <- BA.create (gcm_alignedSize size) (return . const ())
	let dstOff = gcm_alignedOffset dst
	BA.withByteArray dst $ \dptr ->
		BA.withByteArray src $ \sptr ->
			copyBytes (dptr `plusPtr` dstOff) (sptr `plusPtr` srcOff) size
	return (dst, dstOff)

gcm_init
	:: BA.ByteArrayAccess iv
	=> (Ptr Word8 -> Ptr Word8)
	-> FunPtr NettleCryptFunc
	-> BA.ScrubbedBytes -> iv -> NettleGCM
gcm_init encctxoffset encrypt encctx iv = unsafeDupablePerformIO $
	BA.withByteArray iv $ \ivptr ->
	BA.withByteArray encctx $ \encctxptr -> do
	h <- BA.create (gcm_alignedSize c_gcm_key_size) $ \hptr ->
		c_gcm_set_key (alignPtr hptr gcm_alignment) (encctxoffset encctxptr) encrypt
	ctx <- BA.create (gcm_alignedSize c_gcm_ctx_size) $ \ctxptr ->
		BA.withByteArray h $ \hptr ->
			c_gcm_set_iv (alignPtr ctxptr gcm_alignment) (alignPtr hptr gcm_alignment) (fromIntegral $ BA.length iv) ivptr
	return $ NettleGCM (gcm_alignedOffset ctx) ctx (gcm_alignedOffset h) h

-- independent of cipher
gcm_update
	:: BA.ByteArrayAccess ba => NettleGCM -> ba -> NettleGCM
gcm_update (NettleGCM offCtx ctx offKey h) indata = unsafeDupablePerformIO $ do
	(ctx', offCtx') <- gcm_copyAligned c_gcm_ctx_size ctx offCtx
	BA.withByteArray ctx' $ \ctxptr ->
		BA.withByteArray h $ \hptr ->
			BA.withByteArray indata $ \indataptr ->
				c_gcm_update (ctxptr `plusPtr` offCtx') (hptr `plusPtr` offKey) (fromIntegral $ BA.length indata) indataptr >>
					return (NettleGCM offCtx' ctx' offKey h)

gcm_crypt
	:: (BA.ByteArrayAccess bin,
	    BA.ByteArray bout)
	=> NettleGCMMode
	-> (Ptr Word8 -> Ptr Word8)
	-> FunPtr NettleCryptFunc
	-> BA.ScrubbedBytes -> NettleGCM -> bin -> (bout, NettleGCM)
gcm_crypt mode encctxoffset encrypt encctx (NettleGCM offCtx ctx offKey h) indata = unsafeDupablePerformIO $ do
	(ctx', offCtx') <- gcm_copyAligned c_gcm_ctx_size ctx offCtx
	BA.withByteArray ctx' $ \ctxptr ->
		BA.withByteArray h $ \hptr ->
			BA.withByteArray encctx $ \encctxptr ->
				BA.withByteArray indata $ \indataptr -> do
					outdata <- BA.create (BA.length indata) $ \outptr ->
						mode (ctxptr `plusPtr` offCtx') (hptr `plusPtr` offKey) (encctxoffset encctxptr) encrypt (fromIntegral $ BA.length indata) outptr indataptr
					return (outdata, NettleGCM offCtx' ctx' offKey h)

gcm_digest
	:: (Ptr Word8 -> Ptr Word8)
	-> FunPtr NettleCryptFunc
	-> BA.ScrubbedBytes -> NettleGCM -> Int -> CCT.AuthTag
gcm_digest encctxoffset encrypt encctx (NettleGCM offCtx ctx offKey h) taglen = unsafeDupablePerformIO $ do
	(ctx', offCtx') <- gcm_copyAligned c_gcm_ctx_size ctx offCtx
	BA.withByteArray ctx' $ \ctxptr ->
		BA.withByteArray h $ \hptr ->
			BA.withByteArray encctx $ \encctxptr -> do
				tag <- BA.create (fromIntegral taglen) $ \tagptr ->
					callNettleGcmDigest (ctxptr `plusPtr` offCtx') (hptr `plusPtr` offKey) (encctxoffset encctxptr) encrypt (fromIntegral taglen) tagptr
				return $ CCT.AuthTag tag

stream_crypt
	:: (BA.ByteArrayAccess bin,
	    BA.ByteArray bout)
	=> NettleCryptFunc
	-> BA.ScrubbedBytes -> bin -> (bout, BA.ScrubbedBytes)
stream_crypt crypt ctx indata = let ctx' = copyScrubbedBytes ctx in
	unsafeDupablePerformIO $
	BA.withByteArray ctx' $ \ctxptr ->
	BA.withByteArray indata $ \indataptr -> do
	outdata <- BA.create (BA.length indata) $ \outptr ->
		crypt ctxptr (fromIntegral $ BA.length indata) outptr indataptr
	return (outdata, ctx')