{-# OPTIONS_GHC -fno-warn-type-defaults #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} module Main where import Control.Applicative ((<|>)) import qualified Crypto.Hash.SHA256 as SHA256 import qualified Crypto.Hash.SHA512 as SHA512 import qualified Crypto.DRBG.HMAC as DRBG import qualified Data.Attoparsec.ByteString.Char8 as A import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as B8 import qualified Data.ByteString.Base16 as B16 import Test.Tasty import Test.Tasty.HUnit -- CAVP source: -- -- https://raw.githubusercontent.com/coruus/nist-testvectors/refs/heads/master/csrc.nist.gov/groups/STM/cavp/documents/drbg/drbgtestvectors/drbgvectors_pr_true/HMAC_DRBG.txt -- -- spec: -- -- https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Algorithm-Validation-Program/documents/drbg/DRBGVS.pdf main :: IO () main = do sha256_vectors <- BS.readFile "etc/HMAC_DRBG_SHA256.txt" sha512_vectors <- BS.readFile "etc/HMAC_DRBG_SHA512.txt" let sha256_cases = case A.parseOnly parse_sha256_blocks sha256_vectors of Left _ -> error "ppad-hmac-drbg (test): parse error" Right cs -> cs sha512_cases = case A.parseOnly parse_sha512_blocks sha512_vectors of Left _ -> error "ppad-hmac-drbg (test): parse error" Right cs -> cs defaultMain (cavp_14_3 sha256_cases sha512_cases) cavp_14_3 :: [Case] -> [Case] -> TestTree cavp_14_3 cs ds = testGroup "CAVP 14.3" [ testGroup "SHA-256" (fmap (execute SHA256.hmac) cs) , testGroup "SHA-512" (fmap (execute SHA512.hmac) ds) ] -- test case spec data Case = Case { caseCount :: !Int -- instantiate , caseEntropy0 :: !BS.ByteString , caseNonce :: !BS.ByteString , casePs :: !BS.ByteString , caseV0 :: !BS.ByteString , caseK0 :: !BS.ByteString -- first generate , caseAddl1 :: !BS.ByteString , caseEntropy1 :: !BS.ByteString , caseV1 :: !BS.ByteString , caseK1 :: !BS.ByteString -- second generate , caseAddl2 :: !BS.ByteString , caseEntropy2 :: !BS.ByteString , caseV2 :: !BS.ByteString , caseK2 :: !BS.ByteString , caseReturned :: !BS.ByteString } deriving Show -- execute test case execute :: DRBG.HMAC -> Case -> TestTree execute hmac Case {..} = testCase ("count " <> show caseCount) $ do let bytes = fromIntegral (BS.length caseReturned) drbg <- DRBG.new hmac caseEntropy0 caseNonce casePs v0 <- DRBG._read_v drbg k0 <- DRBG._read_k drbg assertEqual "v0" v0 caseV0 assertEqual "k0" k0 caseK0 DRBG.reseed caseEntropy1 caseAddl1 drbg _ <- DRBG.gen mempty bytes drbg v1 <- DRBG._read_v drbg k1 <- DRBG._read_k drbg assertEqual "v1" v1 caseV1 assertEqual "k1" k1 caseK1 DRBG.reseed caseEntropy2 caseAddl2 drbg returned <- DRBG.gen mempty bytes drbg v2 <- DRBG._read_v drbg k2 <- DRBG._read_k drbg assertEqual "returned_bytes" returned caseReturned assertEqual "v2" v2 caseV2 assertEqual "k2" k2 caseK2 -- CAVP vector parsers hex_digit :: A.Parser Char hex_digit = A.satisfy hd where hd c = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') parse_hex :: A.Parser BS.ByteString parse_hex = (B16.decodeLenient . B8.pack) <$> A.many1 hex_digit parse_kv :: BS.ByteString -> A.Parser BS.ByteString parse_kv k = A.string k *> A.skipSpace *> A.char '=' *> parse_v where parse_v = (A.endOfLine *> pure mempty) <|> (A.skipSpace *> parse_hex <* A.endOfLine) parse_case :: A.Parser Case parse_case = do caseCount <- A.string "COUNT = " *> A.decimal <* A.endOfLine caseEntropy0 <- parse_kv "EntropyInput" caseNonce <- parse_kv "Nonce" casePs <- parse_kv "PersonalizationString" A.string "** INSTANTIATE:" *> A.endOfLine caseV0 <- parse_kv "\tV" caseK0 <- parse_kv "\tKey" caseAddl1 <- parse_kv "AdditionalInput" caseEntropy1 <- parse_kv "EntropyInputPR" A.string "** GENERATE (FIRST CALL):" *> A.endOfLine caseV1 <- parse_kv "\tV" caseK1 <- parse_kv "\tKey" caseAddl2 <- parse_kv "AdditionalInput" caseEntropy2 <- parse_kv "EntropyInputPR" caseReturned <- parse_kv "ReturnedBits" A.string "** GENERATE (SECOND CALL):" *> A.endOfLine caseV2 <- parse_kv "\tV" caseK2 <- parse_kv "\tKey" return Case {..} parse_cases :: A.Parser [Case] parse_cases = parse_case `A.sepBy` A.endOfLine parse_sha256_header :: A.Parser () parse_sha256_header = A.string "[SHA-256]" *> A.endOfLine *> A.skipMany1 boring *> A.endOfLine where boring = A.char '[' *> A.skipWhile (/= ']') *> A.char ']' *> A.endOfLine parse_sha256_block :: A.Parser [Case] parse_sha256_block = parse_sha256_header *> parse_cases <* A.endOfLine parse_sha256_blocks :: A.Parser [Case] parse_sha256_blocks = concat <$> A.many1 parse_sha256_block parse_sha512_header :: A.Parser () parse_sha512_header = A.string "[SHA-512]" *> A.endOfLine *> A.skipMany1 boring *> A.endOfLine where boring = A.char '[' *> A.skipWhile (/= ']') *> A.char ']' *> A.endOfLine parse_sha512_block :: A.Parser [Case] parse_sha512_block = parse_sha512_header *> parse_cases <* A.endOfLine parse_sha512_blocks :: A.Parser [Case] parse_sha512_blocks = concat <$> A.many1 parse_sha512_block