crypto-api-0.6: Crypto/Padding.hs
{-|
- Maintainer: Thomas.DuBuisson@gmail.com
- Stability: beta
- Portability: portable
-
- PKCS5 (RFC 1423) and IPSec ESP (RFC 4303) padding methods are implemented both
- as trivial functions operating on bytestrings and as 'Put' routines usable from
- the "Data.Serialize" module. These methods do not work for algorithms or pad
- sizes in excess of 255 bytes (2040 bits, so extremely large as far as cipher needs
- are concerned).
-}
module Crypto.Padding
(
-- * PKCS5 (RFC 1423) based [un]padding routines
padPKCS5
, padBlockSize
, putPaddedPKCS5
, unpadPKCS5safe
, unpadPKCS5
-- * ESP (RFC 4303) [un]padding routines
, padESP, unpadESP
, padESPBlockSize
, putPadESPBlockSize, putPadESP
) where
import Data.Serialize.Put
import Crypto.Classes
import Crypto.Types
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as L
-- |PKCS5 (aka RFC1423) padding method.
-- This method will not work properly for pad modulos > 256
padPKCS5 :: ByteLength -> B.ByteString -> B.ByteString
padPKCS5 len bs = runPut $ putPaddedPKCS5 len bs
-- |
-- @
-- putPaddedPKCS5 m bs
-- @
--
-- Will pad out @bs@ to a byte multiple
-- of @m@ and put both the bytestring and it's padding via 'Put'
-- (this saves on copying if you are already using Cereal).
putPaddedPKCS5 :: ByteLength -> B.ByteString -> Put
putPaddedPKCS5 0 bs = putByteString bs >> putWord8 1
putPaddedPKCS5 len bs = putByteString bs >> putByteString pad
where
pad = B.replicate padLen padValue
r = len - (B.length bs `rem` len)
padLen = if r == 0 then len else r
padValue = fromIntegral padLen
-- |PKCS5 (aka RFC1423) padding method using the BlockCipher instance
-- to determine the pad size.
padBlockSize :: BlockCipher k => k -> B.ByteString -> B.ByteString
padBlockSize k = runPut . putPaddedBlockSize k
-- |Leverages 'putPaddedPKCS5' to put the bytestring and padding
-- of sufficient length for use by the specified block cipher.
putPaddedBlockSize :: BlockCipher k => k -> B.ByteString -> Put
putPaddedBlockSize k bs = putPaddedPKCS5 (blockSizeBytes `for` k) bs
-- | unpad a strict bytestring padded in the typical PKCS5 manner.
-- This routine verifies all pad bytes and pad length match correctly.
unpadPKCS5safe :: B.ByteString -> Maybe B.ByteString
unpadPKCS5safe bs
| bsLen > 0 && B.all (== padLen) pad && B.length pad == pLen = Just msg
| otherwise = Nothing
where
bsLen = B.length bs
padLen = B.last bs
pLen = fromIntegral padLen
(msg,pad) = B.splitAt (bsLen - pLen) bs
-- |unpad a strict bytestring without checking the pad bytes and length any more than necessary.
unpadPKCS5 :: B.ByteString -> B.ByteString
unpadPKCS5 bs = if bsLen == 0 then bs else msg
where
bsLen = B.length bs
padLen = B.last bs
pLen = fromIntegral padLen
(msg,_) = B.splitAt (bsLen - pLen) bs
-- | Pad a bytestring to the IPSEC esp specification
--
-- > padESP m payload
--
-- is equivilent to:
--
-- @
-- (msg) (padding) (length field)
-- B.concat [payload, B.pack [1,2,3,4..], B.pack [padLen]]
-- @
--
-- Where:
--
-- * the msg is any payload, including TFC.
--
-- * the padding is <= 255
--
-- * the length field is one byte.
--
-- Notice the result bytesting length remainder `r` equals zero. The lack
-- of a \"next header\" field means this function is not directly useable for
-- an IPSec implementation (copy/paste the 4 line function and add in a
-- \"next header\" field if you are making IPSec ESP).
padESP :: Int -> B.ByteString -> B.ByteString
padESP i bs = runPut (putPadESP i bs)
-- | Like padESP but use the BlockCipher instance to determine padding size
padESPBlockSize :: BlockCipher k => k -> B.ByteString -> B.ByteString
padESPBlockSize k bs = runPut (putPadESPBlockSize k bs)
-- | Like putPadESP but using the BlockCipher instance to determine padding size
putPadESPBlockSize :: BlockCipher k => k -> B.ByteString -> Put
putPadESPBlockSize k bs = putPadESP (blockSizeBytes `for` k) bs
-- | Pad a bytestring to the IPSEC ESP specification using 'Put'.
-- This can reduce copying if you are already using 'Put'.
putPadESP :: Int -> B.ByteString -> Put
putPadESP 0 bs = putByteString bs >> putWord8 0
putPadESP l bs = do
putByteString bs
putByteString pad
putWord8 pLen
where
pad = B.take padLen espPad
padLen = l - ((B.length bs + 1) `rem` l)
pLen = fromIntegral padLen
-- A static espPad allows reuse of a single B.pack'ed pad for all calls to padESP
espPad = B.pack [1..255]
-- | unpad and return the padded message (Nothing is returned if the padding is invalid)
unpadESP :: B.ByteString -> Maybe B.ByteString
unpadESP bs =
if bsLen == 0 || B.take pLen pad /= B.take pLen espPad
then Nothing
else Just msg
where
bsLen = B.length bs
padLen = B.last bs
pLen = fromIntegral padLen
(msg,pad) = B.splitAt (bsLen - (pLen + 1)) bs