commit f3f2ea5d9e1058547bdaac505420ab976f542039
parent 36c1a780cf1b7a3fd305c224298d3bf0a2afc6e6
Author: Jared Tobin <jared@jtobin.io>
Date:   Mon, 30 Sep 2024 11:33:33 +0400
lib: develop bulk of lib
Diffstat:
4 files changed, 131 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1 @@
+dist-newstyle
diff --git a/lib/Crypto/DRBG/HMAC.hs b/lib/Crypto/DRBG/HMAC.hs
@@ -1,3 +1,109 @@
+{-# OPTIONS_GHC -funbox-small-strict-fields #-}
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE LambdaCase #-}
 
 module Crypto.DRBG.HMAC where
 
+import qualified Crypto.Hash.SHA256 as SHA256
+import qualified Data.ByteString as BS
+import qualified Data.ByteString.Builder as BSB
+import Data.Word (Word64)
+
+-- keystroke saver
+fi :: (Integral a, Num b) => a -> b
+fi = fromIntegral
+{-# INLINE fi #-}
+
+_RESEED_INTERVAL :: Word64
+_RESEED_INTERVAL = (2 :: Word64) ^ (48 :: Word64)
+
+data HMAC = HMAC
+  !(BS.ByteString -> BS.ByteString -> BS.ByteString)
+  {-# UNPACK #-} !Word64
+
+data DRBG = DRBG
+                 !HMAC              -- hmac function & outlen
+  {-# UNPACK #-} !BS.ByteString     -- v
+  {-# UNPACK #-} !BS.ByteString     -- key
+  {-# UNPACK #-} !Word64            -- reseed_counter
+
+instance Show DRBG where
+  show (DRBG _ v k r) = "DRBG " <> show v <> " " <> show k <> " " <> show r
+
+-- dumb strict pair
+data Pair a b = Pair !a !b
+  deriving Show
+
+update
+  :: BS.ByteString
+  -> DRBG
+  -> DRBG
+update provided_data (DRBG h@(HMAC hmac _) v0 k0 r) =
+    let !k1 = hmac k0 (suf 0x00 v0)
+        !v1 = hmac k1 v0
+    in  if   BS.null provided_data
+        then (DRBG h v1 k1 r)
+        else let !k2 = hmac k1 (suf 0x01 v1)
+                 !v2 = hmac k2 v1
+             in  DRBG h v2 k2 r
+  where
+    suf byte bs = BS.toStrict
+      . BSB.toLazyByteString
+      $ BSB.byteString bs <> BSB.word8 byte <> BSB.byteString provided_data
+
+instantiate
+  :: (BS.ByteString -> BS.ByteString -> BS.ByteString)
+  -> BS.ByteString
+  -> BS.ByteString
+  -> BS.ByteString
+  -> DRBG
+instantiate hmac entropy nonce ps =
+    let drbg = DRBG (HMAC hmac outlen) v0 k0 1
+    in  update seed_material drbg
+  where
+    seed_material = entropy <> nonce <> ps
+    outlen = fi (BS.length (hmac mempty mempty)) -- UX hack, costs 1 hmac call
+    k0 = BS.replicate (fi outlen) 0x00
+    v0 = BS.replicate (fi outlen) 0x01
+
+reseed :: DRBG -> BS.ByteString -> BS.ByteString -> DRBG
+reseed drbg entropy addl =
+    let !(DRBG hmac v k _) = update seed_material drbg
+    in  DRBG hmac v k 1
+  where
+    seed_material = entropy <> addl
+
+generate
+  :: BS.ByteString
+  -> Word64
+  -> DRBG
+  -> (BS.ByteString, DRBG)
+generate addl bytes drbg0@(DRBG h@(HMAC hmac outlen) _ _ r)
+    | r > _RESEED_INTERVAL = error "ppad-hmac-drbg: DRBG reseed required"
+    | otherwise =
+        let !(Pair temp drbg1) = go mempty 0 v1
+
+            !returned_bits = BS.take (fi bytes) temp
+            !drbg2 = update addl drbg1
+
+        in  (returned_bits, drbg2)
+  where
+    !(DRBG _ v1 k1 _)
+      | BS.null addl = drbg0
+      | otherwise = update addl drbg0
+
+    go !acc !len !vl
+      | len < bytes =
+          let nv   = hmac k1 vl
+              nacc = acc <> BSB.byteString nv
+              nlen = len + outlen
+          in  go nacc nlen nv
+
+      -- take opportunity to update reseed_counter here
+      | otherwise =
+          let facc = BS.toStrict . BSB.toLazyByteString $ acc
+          in  Pair facc (DRBG h vl k1 (succ r))
+
+
+-- XX test against
+-- 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
diff --git a/ppad-hmac-drbg.cabal b/ppad-hmac-drbg.cabal
@@ -30,3 +30,20 @@ library
     , bytestring >= 0.9 && < 0.13
     , ppad-sha256 >= 0.1.0 && < 0.2.0
 
+test-suite hmac-drbg-tests
+  type:                exitcode-stdio-1.0
+  default-language:    Haskell2010
+  hs-source-dirs:      test
+  main-is:             Main.hs
+
+  ghc-options:
+    -rtsopts -Wall -O2
+
+  build-depends:
+      base
+    , base16-bytestring
+    , bytestring
+    , ppad-hmac-drbg
+    , tasty
+    , tasty-hunit
+
diff --git a/test/Main.hs b/test/Main.hs
@@ -0,0 +1,7 @@
+
+module Main where
+
+import qualified Crypto.DRBG.HMAC as DRBG
+import qualified Data.ByteString as BS
+import qualified Data.ByteString.Base16 as B16
+