Z-Botan-0.1.1.0: test/Utils.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
module Utils where
import Control.Applicative
import Control.Monad
import Z.IO
import qualified Z.IO.FileSystem as FS
import Z.Data.ASCII
import Z.Data.Vector.Hex
import qualified Z.Data.Parser as P
import Z.Data.Parser.Numeric (decLoopIntegerFast)
import Z.Data.CBytes (CBytes)
import qualified Z.Data.CBytes as CB
import qualified Z.Data.Vector as V
import Prelude hiding (lines, mod)
-- import Data.IORef
-- | Parse test data vector files.
-- See `./third_party/botan/src/tests/data/`.
parseNamedTestVector :: HasCallStack => P.Parser (x, Bool) -> CBytes -> IO [(V.Bytes, x)]
parseNamedTestVector p path = do
withResource (FS.initFile path FS.O_RDONLY FS.DEFAULT_FILE_MODE) $ \ f -> do
bi <- newBufferedInput f
let loopRead = do
name <- readParser algoName bi
(lines, eof) <- readParser p bi
if eof
then return [(name, lines)]
else do
rest <- loopRead
return ((name, lines):rest)
loopRead
where
algoName = do
P.skipWhile (/= BRACKET_LEFT)
P.skipWord8
n <- P.takeWhile1 (/= BRACKET_RIGHT)
P.skipWord8
return n
-- | Parse @key = value@ or @key = value@ or @key =@ line
parseKeyValueLine :: V.Bytes -> P.Parser V.Bytes
parseKeyValueLine k1 = do
P.bytes k1
P.skipWhile (== SPACE)
P.word8 EQUAL
P.skipWhile (== SPACE)
-- some lines contains trailing spaces, e.g., tls_prf.vec
V.dropWhileR isSpace <$> P.takeWhile (/= NEWLINE)
parseHashTestVector :: HasCallStack => CBytes -> IO [(V.Bytes, [(V.Bytes, V.Bytes)])]
parseHashTestVector = parseNamedTestVector (go [])
where
go acc = do
P.skipWhile (\ w -> w /= LETTER_I && w /= BRACKET_LEFT && w /= HASH)
w <- P.peekMaybe
case w of
Nothing -> return (acc, True)
Just HASH -> do
P.skipWhile (/= NEWLINE)
P.skipWord8
go acc
Just BRACKET_LEFT -> return (acc, False)
_ -> do
i <- parseKeyValueLine "In"
P.skipWord8
o <- parseKeyValueLine "Out"
go ((hexDecode' i, hexDecode' o):acc)
parseBlockCipherTestVector :: HasCallStack => CBytes -> IO [(V.Bytes, [(V.Bytes, V.Bytes, V.Bytes)])]
parseBlockCipherTestVector = parseNamedTestVector (go [])
where
go acc = do
P.skipWhile (\ w -> w /= LETTER_K && w /= BRACKET_LEFT && w /= HASH)
w <- P.peekMaybe
case w of
Nothing -> return (acc, True)
Just HASH -> do
P.skipWhile (/= NEWLINE)
P.skipWord8
go acc
Just BRACKET_LEFT -> return (acc, False)
_ -> do
key <- parseKeyValueLine "Key"
P.skipWord8
i <- parseKeyValueLine "In"
P.skipWord8
o <- parseKeyValueLine "Out"
go ((hexDecode' key, hexDecode' i, hexDecode' o):acc)
parseCipherModeTestVector :: HasCallStack => CBytes -> IO [(V.Bytes, [(V.Bytes, V.Bytes, V.Bytes, V.Bytes)])]
parseCipherModeTestVector = parseNamedTestVector (go [])
where
go acc = do
P.skipWhile (\ w -> w /= LETTER_K && w /= LETTER_N && w /= BRACKET_LEFT && w /= HASH)
w <- P.peekMaybe
case w of
Nothing -> return (acc, True)
Just HASH -> do
P.skipWhile (/= NEWLINE)
P.skipWord8
go acc
Just BRACKET_LEFT -> return (acc, False)
Just w' -> do
(key, nonce) <- if w' == LETTER_K
then do
key <- parseKeyValueLine "Key"
P.skipWord8
nonce <- parseKeyValueLine "Nonce"
P.skipWord8
return (key, nonce)
else do
nonce <- parseKeyValueLine "Nonce"
P.skipWord8
key <- parseKeyValueLine "Key"
P.skipWord8
return (key, nonce)
i <- parseKeyValueLine "In"
P.skipWord8
o <- parseKeyValueLine "Out"
go ((hexDecode' key, hexDecode' nonce, hexDecode' i, hexDecode' o):acc)
parseCipherAEADTestVector :: HasCallStack => CBytes -> IO [(V.Bytes, [(V.Bytes, V.Bytes, V.Bytes, V.Bytes, V.Bytes)])]
parseCipherAEADTestVector = parseNamedTestVector (go [])
where
go acc = do
P.skipWhile (\ w -> w /= LETTER_K && w /= LETTER_N && w /= BRACKET_LEFT && w /= HASH)
w <- P.peekMaybe
case w of
Nothing -> return (acc, True)
Just HASH -> do
P.skipWhile (/= NEWLINE)
P.skipWord8
go acc
Just BRACKET_LEFT -> return (acc, False)
Just w' -> do
(key, nonce) <- if w' == LETTER_K
then do
key <- parseKeyValueLine "Key"
P.skipWord8
nonce <- parseKeyValueLine "Nonce"
P.skipWord8
return (key, nonce)
else do
nonce <- parseKeyValueLine "Nonce"
P.skipWord8
key <- parseKeyValueLine "Key"
P.skipWord8
return (key, nonce)
i <- parseKeyValueLine "In"
P.skipWord8
ad <- parseKeyValueLine "AD"
P.skipWord8
o <- parseKeyValueLine "Out"
go ((hexDecode' key, hexDecode' nonce, hexDecode' i, hexDecode' ad, hexDecode' o):acc)
-- | Parse test data vector files.
-- See `./third_party/botan/src/tests/data/`.
parseTestVector ::
HasCallStack =>
-- | (txt, eof)
P.Parser ([x], Bool) ->
-- | path
CBytes ->
IO [x]
parseTestVector p path = do
withResource (FS.initFile path FS.O_RDONLY FS.DEFAULT_FILE_MODE) $ \f -> do
bi <- newBufferedInput f
let loopRead = do
(txt, eof) <- readParser p bi
if eof
then return txt
else do
rest <- loopRead
return $ txt ++ rest
loopRead
-- | Parse test data vectors of the form:
-- -- @Password = @
-- -- @Passhash = @
-- See `./third_party/botan/src/tests/data/passhash/bcrypt.vec`.
parsePasswdHashTestVector :: HasCallStack => CBytes -> IO [(V.Bytes, V.Bytes)]
parsePasswdHashTestVector = parseTestVector $ h []
where
h acc = do
P.skipWhile $ \c ->
c /= LETTER_P -- goto @Password = @ or @Passhash = @
&& c /= HASH -- deal with comment
c <- P.peekMaybe
case c of
Nothing -> pure (acc, True) -- end of file
Just HASH -> do
P.skipWhile (/= NEWLINE) -- line comment
P.skipWord8 -- skip @\n@
h acc
_ -> do
passwd <- parseKeyValueLine "Password"
P.skipWord8
passhash <- parseKeyValueLine "Passhash"
h $ (hexDecode' passwd, passhash) : acc
-- | Parse test data vectors of the form
--
-- -- @Salt == @
-- -- @Label == @
-- -- @Secret == @
-- -- @Output == @
--
-- and (unused) possible
--
-- -- @IKM == @
-- -- @XTS == @
--
-- See `./third_party/botan/src/tests/data/kdf/hkdf.vec`.
parseKDFTestVector :: HasCallStack => CBytes -> IO [(V.Bytes, [(V.Bytes, V.Bytes, V.Bytes, V.Bytes)])]
parseKDFTestVector = parseNamedTestVector (go [])
where
go acc = do
P.skipWhile (\ w -> w /= LETTER_S
&& w /= LETTER_L
&& w /= LETTER_O
&& w /= LETTER_I
&& w /= LETTER_X
&& w /= BRACKET_LEFT && w /= HASH)
w <- P.peekMaybe
case w of
Nothing -> return (acc, True)
Just HASH -> do
P.skipWhile (/= NEWLINE)
P.skipWord8
go acc
Just BRACKET_LEFT -> return (acc, False)
_ -> do
-- skip possible IKM and XTS
ikm <- parseKeyValueLine "IKM" <|> pure mempty
unless (V.null ikm)
P.skipWord8
xts <- parseKeyValueLine "XTS" <|> pure mempty
unless (V.null xts)
P.skipWord8
salt <- parseKeyValueLine "Salt" <|> pure mempty
-- and here the salt's value may be empty...: in the format: Salt = <\n>.
P.peekMaybe >>= \case Just NEWLINE -> P.skipWord8
_ -> return ()
label <- parseKeyValueLine "Label" <|> pure mempty
unless (V.null label)
P.skipWord8
secret <- parseKeyValueLine "Secret"
P.skipWord8
-- in some test data files the "salt" is placed after "secret", e.g., "kdf1.vec"
salt <- if V.null salt
then do salt <- parseKeyValueLine "Salt" <|> pure mempty
-- and here the salt's value may be empty...: in the format: Salt = <\n>.
P.peekMaybe >>= \case Just NEWLINE -> P.skipWord8
_ -> return ()
return salt
else return salt
-- in some test data files the "label" is placed after "secret", e.g., "sp800_56a.vec"
label <- if V.null label
then do label <- parseKeyValueLine "Label" <|> pure mempty
unless (V.null label)
P.skipWord8
return label
else return label
o <- parseKeyValueLine "Output"
go ((hexDecode' salt, hexDecode' label, hexDecode' secret, hexDecode' o):acc)
-- | Parse test data vectors of the form
--
-- -- @Salt == @
-- -- @Iterations == @
-- -- @Passphrase == @
-- -- @Output == @
--
-- See `./third_party/botan/src/tests/data/pbkdf/pbkdf1.vec`.
parsePBKDFTestVector :: HasCallStack => CBytes -> IO [(V.Bytes, [(V.Bytes, Int, CBytes, V.Bytes)])]
parsePBKDFTestVector = parseNamedTestVector (go [])
where
go acc = do
P.skipWhile (\ w -> w /= LETTER_S
&& w /= LETTER_I
&& w /= LETTER_P
&& w /= LETTER_O
&& w /= BRACKET_LEFT && w /= HASH)
w <- P.peekMaybe
case w of
Nothing -> return (acc, True)
Just HASH -> do
P.skipWhile (/= NEWLINE)
P.skipWord8
go acc
Just BRACKET_LEFT -> return (acc, False)
_ -> do
-- in some cases the "Salt" and "Iterations" is after "Passphrase"
salt <- parseKeyValueLine "Salt" <|> pure mempty
P.peekMaybe >>= \case Just NEWLINE -> P.skipWord8
_ -> return ()
iter <- parseKeyValueLine "Iterations" <|> pure mempty
P.peekMaybe >>= \case Just NEWLINE -> P.skipWord8
_ -> return ()
passphrase <- parseKeyValueLine "Passphrase"
P.skipWord8
salt <- if V.null salt
then do salt <- parseKeyValueLine "Salt" <|> pure mempty
P.peekMaybe >>= \case Just NEWLINE -> P.skipWord8
_ -> return ()
return salt
else return salt
iter <- if V.null iter
then do iter <- parseKeyValueLine "Iterations" <|> pure mempty
P.peekMaybe >>= \case Just NEWLINE -> P.skipWord8
_ -> return ()
return iter
else return iter
o <- parseKeyValueLine "Output"
go ( (hexDecode' salt
, (fromIntegral . decLoopIntegerFast) iter
, CB.fromBytes passphrase
, hexDecode' o):acc)
-- | Parse test data vectors of the form
--
-- -- @IV == @ (possible)
-- -- @Key == @
-- -- @In == @
-- -- @Out == @
--
-- See `./third_party/botan/src/tests/data/mac/cbcmac.vec`.
parseMACTestVector :: HasCallStack => CBytes -> IO [(V.Bytes, [(Maybe V.Bytes, V.Bytes, V.Bytes, V.Bytes)])]
parseMACTestVector = parseNamedTestVector (go [])
where
go acc = do
P.skipWhile (\ w -> w /= LETTER_I
&& w /= LETTER_K
&& w /= LETTER_O
&& w /= BRACKET_LEFT && w /= HASH)
w <- P.peekMaybe
case w of
Nothing -> return (acc, True)
Just HASH -> do
P.skipWhile (/= NEWLINE)
P.skipWord8
go acc
Just BRACKET_LEFT -> return (acc, False)
_ -> do
-- in some cases the "Salt" and "Iterations" is after "Passphrase"
iv <- optional $ parseKeyValueLine "IV"
case iv of
Just iv' -> unless (V.null iv') P.skipWord8
Nothing -> return ()
key <- parseKeyValueLine "Key"
P.skipWord8
in_ <- parseKeyValueLine "In"
P.skipWord8
o <- parseKeyValueLine "Out"
go ((fmap hexDecode' iv, hexDecode' key, hexDecode' in_, hexDecode' o):acc)
skipComment :: (t -> P.Parser b) -> t -> P.Parser b
{-# INLINE skipComment #-}
skipComment h acc = do
P.skipWhile (/= NEWLINE)
P.skipWord8
h acc
-- | Parse test data vectors of the form:
--
-- -- @Key == @
-- -- @KEK == @
-- -- @Output == @
--
-- See `./third_party/botan/src/tests/data/keywrap/rfc3394.vec`.
parseKeyWrapVec :: HasCallStack => CBytes -> IO [(V.Bytes, V.Bytes, V.Bytes)]
parseKeyWrapVec = parseTestVector (h [])
where
h acc = do
P.skipWhile (\ w -> w /= LETTER_K && w /= LETTER_O && w /= HASH)
w <- P.peekMaybe
case w of
Nothing -> return (acc, True)
Just HASH -> skipComment h acc
_ -> do
key <- parseKeyValueLine "Key"
P.skipWord8
kek <- parseKeyValueLine "KEK"
P.skipWord8
o <- parseKeyValueLine "Output"
h ((hexDecode' key, hexDecode' kek, hexDecode' o) : acc)
-- | Parse test data vectors of the form:
--
-- -- [label] -- [label]
-- -- @Key == @ -- @Key == @
-- -- @Input == @ -- @Output == @
-- -- @Output == @ -- @Input == @
--
-- See `./third_party/botan/src/tests/data/keywrap/`.
-- parseNistKeyWrapVec :: HasCallStack => CBytes -> IO [(V.Bytes ,[(V.Bytes, V.Bytes, V.Bytes)])]
-- parseNistKeyWrapVec = parseNamedTestVector (h [])
-- where
-- h acc = do
-- P.skipWhile $ \ w -> w /= LETTER_K
-- && w /= LETTER_O
-- && w /= BRACKET_LEFT && w /= HASH
-- w <- P.peekMaybe
-- case w of
-- Nothing -> return (acc, True)
-- Just HASH -> skipComment h acc
-- Just BRACE_LEFT -> return (acc, False)
-- _ -> do
-- key <- parseKeyValueLine "Key"
-- P.skipWord8
-- resTag <- liftIO $ newIORef False
-- res0 <- parseKeyValueLine "Input" <* (liftIO $ writeIORef resTag True) <|> parseKeyValueLine "Output"
-- P.skipWord8
-- res1 <- parseKeyValueLine "Input" <|> parseKeyValueLine "Output"
-- resTag' <- liftIO $ readIORef resTag
-- let (i, o) = if resTag' then (res0, res1) else (res1, res0)
-- h ((hexDecode' key, hexDecode' i, hexDecode' o) : acc)
parseFPEVec :: HasCallStack => CBytes -> IO [(V.Bytes, V.Bytes, V.Bytes, V.Bytes, V.Bytes)]
parseFPEVec = parseTestVector (h [])
where
h acc = do
P.skipWhile $ \ w -> w /= LETTER_M -- Mod
&& w /= LETTER_I -- In
&& w /= LETTER_O -- Out
&& w /= LETTER_K -- Key
&& w /= LETTER_T -- Tweak
&& w /= HASH
w <- P.peekMaybe
case w of
Nothing -> return (acc, True)
Just HASH -> skipComment h acc
_ -> do
mod <- parseKeyValueLine "Mod" <* P.skipWord8
i <- parseKeyValueLine "In" <* P.skipWord8
o <- parseKeyValueLine "Out" <* P.skipWord8
key <- parseKeyValueLine "Key" <* P.skipWord8
tweak <- parseKeyValueLine "Tweak" <* P.skipWord8
h ((mod, i, o, hexDecode' key, hexDecode' tweak) : acc)