module Crypto.Conduit
(
sinkHash
, hashFile
, sinkHmac
, conduitEncryptEcb
, conduitDecryptEcb
, conduitEncryptCbc
, conduitDecryptCbc
, conduitEncryptCfb
, conduitDecryptCfb
, conduitEncryptOfb
, conduitDecryptOfb
, conduitEncryptCtr
, conduitDecryptCtr
, sourceCtr
, sinkCbcMac
, blocked
, BlockMode(..)
, Block(..)
) where
import Control.Applicative ((<$>))
import Control.Arrow (first)
import Data.Bits (xor)
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as L
import qualified Data.Serialize as S
import Crypto.Classes ((.::.))
import qualified Crypto.Classes as C
import qualified Crypto.HMAC as C
import qualified Crypto.Modes as C
import qualified Crypto.Types as C
import Data.Conduit
import Data.Conduit.Binary (sourceFile)
import Control.Monad.IO.Class (MonadIO, liftIO)
getType :: Monad m => sink input m output -> output
getType = undefined
sinkHash :: (Resource m, C.Hash ctx d) => Sink B.ByteString m d
sinkHash = blocked AnyMultiple blockSize =$ sink
where
sink = sinkState C.initialCtx
push
(const $ fail "sinkHash")
push ctx (Full bs) =
let !ctx' = C.updateCtx ctx bs
in return (ctx', Processing)
push ctx (LastOne bs) =
let !ret = C.finalize ctx bs
in return (error "sinkHash", Done Nothing ret)
blockSize = (C.blockLength .::. getType sink) `div` 8
hashFile :: (MonadIO m, C.Hash ctx d) => FilePath -> m d
hashFile fp = liftIO $ runResourceT (sourceFile fp $$ sinkHash)
sinkHmac :: (Resource m, C.Hash ctx d) => C.MacKey -> Sink B.ByteString m d
sinkHmac (C.MacKey key) = blocked AnyMultiple blockSize =$ sink
where
key' =
case B.length key `compare` blockSize of
GT -> B.append
(S.encode $ C.hashFunc' d key)
(B.replicate (blockSize outputSize) 0x00)
EQ -> key
LT -> B.append key (B.replicate (blockSize B.length key) 0x00)
ko = B.map (`xor` 0x5c) key'
ki = B.map (`xor` 0x36) key'
sink = sinkState (C.updateCtx C.initialCtx ki)
push
(const $ fail "sinkHmac")
push ctx (Full bs) =
let !ctx' = C.updateCtx ctx bs
in return (ctx', Processing)
push ctx (LastOne bs) =
let !inner = C.finalize ctx bs `asTypeOf` d
!outer = C.hash $ L.fromChunks [ko, S.encode inner]
in return (error "sinkHmac", Done Nothing outer)
d = getType sink
blockSize = (C.blockLength .::. d) `div` 8
outputSize = (C.outputLength .::. d) `div` 8
conduitEncryptEcb :: (Resource m, C.BlockCipher k) =>
k
-> Conduit B.ByteString m B.ByteString
conduitEncryptEcb k =
blockCipherConduit k
AnyMultiple
()
(\_ input -> ((), C.encryptBlock k input))
(\_ _ -> fail "conduitEncryptEcb: input has an incomplete final block.")
conduitDecryptEcb :: (Resource m, C.BlockCipher k) =>
k
-> Conduit B.ByteString m B.ByteString
conduitDecryptEcb k =
blockCipherConduit k
AnyMultiple
()
(\_ input -> ((), C.decryptBlock k input))
(\_ _ -> fail "conduitDecryptEcb: input has an incomplete final block.")
conduitEncryptCbc :: (Resource m, C.BlockCipher k) =>
k
-> C.IV k
-> Conduit B.ByteString m B.ByteString
conduitEncryptCbc k iv =
blockCipherConduit k
StrictBlockSize
(S.encode iv)
(\iv' input -> let output = C.encryptBlock k (iv' `zwp` input)
in (output, output))
(\_ _ -> fail "conduitEncryptCbc: input has an incomplete final block.")
conduitDecryptCbc :: (Resource m, C.BlockCipher k) =>
k
-> C.IV k
-> Conduit B.ByteString m B.ByteString
conduitDecryptCbc k iv =
blockCipherConduit k
StrictBlockSize
(S.encode iv)
(\iv' input -> let output = C.decryptBlock k input `zwp` iv'
in (input, output))
(\_ _ -> fail "conduitDecryptCbc: input has an incomplete final block.")
conduitEncryptCfb :: (Resource m, C.BlockCipher k) =>
k
-> C.IV k
-> Conduit B.ByteString m B.ByteString
conduitEncryptCfb k iv =
blockCipherConduit k
StrictBlockSize
(S.encode iv)
(\iv' input -> let output = C.encryptBlock k iv' `zwp` input
in (output, output))
(\_ _ -> fail "conduitEncryptCfb: input has an incomplete final block.")
conduitDecryptCfb :: (Resource m, C.BlockCipher k) =>
k
-> C.IV k
-> Conduit B.ByteString m B.ByteString
conduitDecryptCfb k iv =
blockCipherConduit k
StrictBlockSize
(S.encode iv)
(\iv' input -> let output = C.encryptBlock k iv' `zwp` input
in (input, output))
(\_ _ -> fail "conduitDecryptCfb: input has an incomplete final block.")
conduitEncryptOfb :: (Resource m, C.BlockCipher k) =>
k
-> C.IV k
-> Conduit B.ByteString m B.ByteString
conduitEncryptOfb k iv =
blockCipherConduit k
StrictBlockSize
(S.encode iv)
(\iv' input -> let inter = C.encryptBlock k iv'
in (inter, inter `zwp` input))
(\_ _ -> fail "conduitEncryptOfb: input has an incomplete final block.")
conduitDecryptOfb :: (Resource m, C.BlockCipher k) =>
k
-> C.IV k
-> Conduit B.ByteString m B.ByteString
conduitDecryptOfb = conduitEncryptOfb
conduitEncryptCtr :: (Resource m, C.BlockCipher k) =>
k
-> C.IV k
-> (C.IV k -> C.IV k)
-> Conduit B.ByteString m B.ByteString
conduitEncryptCtr k iv incIV =
blockCipherConduit k
StrictBlockSize
iv
(\iv' input -> let !iv'' = incIV iv'
output = C.encryptBlock k (S.encode iv') `zwp` input
in (iv'', output))
(\iv' input -> let output = C.encryptBlock k (S.encode iv') `zwp` input
in return output)
conduitDecryptCtr :: (Resource m, C.BlockCipher k) =>
k
-> C.IV k
-> (C.IV k -> C.IV k)
-> Conduit B.ByteString m B.ByteString
conduitDecryptCtr = conduitEncryptCtr
sourceCtr :: (Resource m, C.BlockCipher k) =>
k
-> C.IV k
-> Source m B.ByteString
sourceCtr k iv = sourceState iv pull
where
pull iv' =
let !iv'' = C.incIV iv'
block = C.encryptBlock k $ S.encode iv'
in return (iv'', Open block)
sinkCbcMac :: (Resource m, C.BlockCipher k) =>
k
-> Sink B.ByteString m B.ByteString
sinkCbcMac k = blocked StrictBlockSize blockSize =$ sink
where
sink = sinkState (B.replicate blockSize 0) push close
push iv (Full input) =
let !iv' = C.encryptBlock k (iv `zwp` input)
in return (iv', Processing)
push iv (LastOne input)
| B.null input = return (error "sinkCbcMac", Done Nothing iv)
| otherwise = fail "sinkCbcMac: input has an incomplete final block."
close _ = fail "sinkCbcMac"
blockSize = (C.blockSize .::. k) `div` 8
blocked :: Resource m =>
BlockMode
-> C.ByteLength
-> Conduit B.ByteString m Block
blocked mode blockSize = conduitState B.empty push close
where
block = case mode of
StrictBlockSize -> blockStrict []
AnyMultiple -> blockAny
where
blockStrict acc bs
| B.length bs < blockSize = (reverse acc, bs)
| otherwise = blockStrict (Full this : acc) rest
where (this, rest) = B.splitAt blockSize bs
blockAny bs
| n >= 1 = first ((:[]) . Full) $ B.splitAt (n * blockSize) bs
| otherwise = ([], bs)
where n = B.length bs `div` blockSize
append bs1 bs2
| B.null bs1 = bs2
| otherwise = B.append bs1 bs2
push acc = return . mk . block . append acc
where
mk (blks, rest) = (rest, Producing blks)
close = return . (:[]) . LastOne
data BlockMode = StrictBlockSize | AnyMultiple
deriving (Eq, Ord, Show, Enum)
data Block = Full B.ByteString | LastOne B.ByteString
deriving (Eq, Ord, Show)
blockCipherConduit :: (Resource m, C.BlockCipher k) =>
k
-> BlockMode
-> s
-> (s -> B.ByteString -> (s, B.ByteString))
-> (s -> B.ByteString -> ResourceT m B.ByteString)
-> Conduit B.ByteString m B.ByteString
blockCipherConduit key mode initialState apply final = blocked mode blockSize =$= conduit
where
blockSize = (C.blockSize .::. key) `div` 8
conduit = conduitState initialState push close
push state (Full input) =
let (!state', !output) = apply state input
in return (state', Producing [output])
push _ (LastOne input) | B.null input =
return (error "blockCipherConduit", Finished Nothing [])
push state (LastOne input) = mk <$> final state input
where mk output = (error "blockCipherConduit", Finished Nothing [output])
close _ = fail "blockCipherConduit"
zwp :: B.ByteString -> B.ByteString -> B.ByteString
zwp a = B.pack . B.zipWith xor a