module Botan.KDF where

import Botan.Low.KDF (KDFName(..))
import qualified Botan.Low.KDF as Low

import Botan.Hash
import Botan.MAC
import Botan.Prelude

-- Sources: https://github.com/randombit/botan/blob/master/src/lib/kdf/kdf.cpp
-- Due to the blurring of mac vs hash, it is difficult to determine whether
-- a given algorithm works with macs, hashes, both, macs and hashes-via-hmac, or hashes and only hmac
-- According to source:
-- HKDF, HKDF-Extract, HKDF-Expand use kdf_create_mac_or_hash but the H literally stands for HMAC so...
-- KDF2, KDF1-18033, KDF1 use Hash
-- TLS-12-PRF is unimplemented, not in documentation (relic of Botan 2?), and uses kdf_create_mac_or_hash
-- X9.42-PRF is deprecated, has a unique create function, and uses Hash
-- SP800-108-Counter, -Feedback, -Pipeline use kdf_create_mac_or_hash
-- SP800-56A has two unique create functions, and uses Hash or HMAC (but trying to use hash fails according to tests)
-- SP800-56C has two unique create functions, and uses macs or hashes

-- TODO: Find out if the algorithms that support both HMAC and Hash syntax are actually different,
-- or if its just substituting HMAC() silently

data KDF
    = HKDF Hash
    | HKDF_Extract Hash
    | HKDF_Expand Hash
    | KDF2 Hash
    | KDF1_18033 Hash
    | KDF1 Hash
    | TLS_12_PRF Hash -- Not mentioned but still in code?
    -- NOTE: Despite documentation stating it only works with SHA-1,
    -- it "works" with many hashes: MD5, RIPEMD-160, SHA-1, all SHA-2, SM3, both Streebog
    -- however I don't think it is correct behavior as the actual code appears to only
    -- allocate a SHA-1 hash context
    -- | X9_42_PRF Hash
    | X9_42_PRF
    -- NOTE: These SP800 accept / require HMAC wrapping
    | SP800_108_Counter Hash
    | SP800_108_Feedback Hash
    | SP800_108_Pipeline Hash
    | SP800_56A Hash
    | SP800_56C Hash 
    deriving (Int -> KDF -> ShowS
[KDF] -> ShowS
KDF -> String
(Int -> KDF -> ShowS)
-> (KDF -> String) -> ([KDF] -> ShowS) -> Show KDF
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> KDF -> ShowS
showsPrec :: Int -> KDF -> ShowS
$cshow :: KDF -> String
show :: KDF -> String
$cshowList :: [KDF] -> ShowS
showList :: [KDF] -> ShowS
Show, KDF -> KDF -> Bool
(KDF -> KDF -> Bool) -> (KDF -> KDF -> Bool) -> Eq KDF
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: KDF -> KDF -> Bool
== :: KDF -> KDF -> Bool
$c/= :: KDF -> KDF -> Bool
/= :: KDF -> KDF -> Bool
Eq)

-- TODO: | SP800_108 SP800_108_Mode MAC

data SP800_108_Mode
    = Counter
    | Feedback
    | Pipeline
    
kdfName :: KDF -> KDFName
kdfName :: KDF -> KDFName
kdfName (HKDF Hash
h)                = KDFName -> KDFName
Low.hkdf (Hash -> KDFName
hashName Hash
h)            -- "HKDF(" <> hashName h <> ")"
kdfName (HKDF_Extract Hash
h)        = KDFName -> KDFName
Low.hkdf_extract (Hash -> KDFName
hashName Hash
h)    -- "HKDF-Extract(" <> hashName h <> ")"
kdfName (HKDF_Expand Hash
h)         = KDFName -> KDFName
Low.hkdf_expand (Hash -> KDFName
hashName Hash
h)     -- "HKDF-Expand(" <> hashName h <> ")"
kdfName (KDF2 Hash
h)                = KDFName -> KDFName
Low.kdf2 (Hash -> KDFName
hashName Hash
h)            -- "KDF2(" <> hashName h <> ")"
kdfName (KDF1_18033 Hash
h)          = KDFName -> KDFName
Low.kdf1_18033 (Hash -> KDFName
hashName Hash
h)      -- "KDF1-18033(" <> hashName h <> ")"
kdfName (KDF1 Hash
h)                = KDFName -> KDFName
Low.kdf1 (Hash -> KDFName
hashName Hash
h)            -- "KDF1(" <> hashName h <> ")"
kdfName (TLS_12_PRF Hash
h)          = KDFName -> KDFName
Low.tls_12_prf (Hash -> KDFName
hashName Hash
h)      -- "TLS-12-PRF(" <> hashName h <> ")"
-- NOTE: Many hashes do not throw an error for this KDF but are not necessarily correct
-- kdfName (X9_42_PRF h)           = "X9.42-PRF(" <> hashName h <> ")"
-- Only SHA-1 is valid for X9_42_PRF according to the source
kdfName KDF
X9_42_PRF               = KDFName -> KDFName
Low.x9_42_prf (Hash -> KDFName
hashName Hash
SHA1)        --"X9.42-PRF(SHA-1)"
kdfName (SP800_108_Counter Hash
h)   = KDFName -> KDFName
Low.sp800_108_counter (Hash -> KDFName
hashName Hash
h)   -- "SP800-108-Counter(HMAC(" <> hashName h <> "))"
kdfName (SP800_108_Feedback Hash
h)  = KDFName -> KDFName
Low.sp800_108_feedback (Hash -> KDFName
hashName Hash
h)  -- "SP800-108-Feedback(HMAC(" <> hashName h <> "))"
kdfName (SP800_108_Pipeline Hash
h)  = KDFName -> KDFName
Low.sp800_108_pipeline (Hash -> KDFName
hashName Hash
h)  -- "SP800-108-Pipeline(HMAC(" <> hashName h <> "))"
kdfName (SP800_56A Hash
h)           = KDFName -> KDFName
Low.sp800_56A (Hash -> KDFName
hashName Hash
h)           -- "SP800-56A(HMAC(" <> hashName h <> "))"
kdfName (SP800_56C Hash
h)           = KDFName -> KDFName
Low.sp800_56C (Hash -> KDFName
hashName Hash
h)           -- "SP800-56C(HMAC(" <> hashName h <> "))"

-- NOTE: This works:
--  > kdf "KDF1(SHA-256)" 32 "Fee fi fo fum!" "English" "Bread"
-- Some have constraints on key length, eg MD5:
--  > kdf "KDF1(MD5)" 32 "Fee fi fo fum!" "English" "Bread"
--  *** Exception: BadParameterException (-32) ...
--  > kdf "KDF1(MD5)" 16 "Fee fi fo fum!" "English" "Bread"
--  "\234\176\202\212A\162\154]\238J\131aKL\142\197"
-- Also this works but shouldnt
--  > kdf "X9.42-PRF(Streebog-256)" 10 "secret" "salt" "label"
--  "\163\224\173\&1>\218$tx\228"

kdf :: KDF -> Int -> ByteString -> ByteString -> ByteString -> ByteString
kdf :: KDF -> Int -> KDFName -> KDFName -> KDFName -> KDFName
kdf KDF
algo Int
outLen KDFName
secret KDFName
salt KDFName
label = IO KDFName -> KDFName
forall a. IO a -> a
unsafePerformIO (IO KDFName -> KDFName) -> IO KDFName -> KDFName
forall a b. (a -> b) -> a -> b
$ KDFName -> Int -> KDFName -> KDFName -> KDFName -> IO KDFName
Low.kdf (KDF -> KDFName
kdfName KDF
algo) Int
outLen KDFName
secret KDFName
salt KDFName
label