import Codec.Crypto.RSA.Pure
import Control.Monad(forM_)
import Crypto.Random
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BSL
import Data.Map.Strict(Map)
import qualified Data.Map.Strict as Map
import Numeric
import System.IO

force :: Show a => Either a b -> b
force (Left  e) = error ("Force failure: " ++ show e)
force (Right x) = x

forceGen :: CryptoRandomGen g => g -> Int -> (BSL.ByteString, g)
forceGen g x = 
  let (bs, g') = force (genBytes x g)
  in (BSL.fromStrict bs, g')

message :: CryptoRandomGen g => g -> (BSL.ByteString, g)
message g =
  let (lenbs, g') = forceGen g 2
      [len0,len1] = BSL.unpack lenbs
      len         = (fromIntegral len1 * 2) + fromIntegral len0
  in forceGen g' len

keyPair :: CryptoRandomGen g => g -> Int -> (PublicKey, PrivateKey, g)
keyPair g size = force (generateKeyPair g size)

hash :: CryptoRandomGen g => g -> (HashInfo, g)
hash g =
  let (hbs, g') = forceGen g 1
      [hb]      = BSL.unpack hbs
  in case hb `mod` 5 of
       0 -> (hashSHA1,   g')
       1 -> (hashSHA224, g')
       2 -> (hashSHA256, g')
       3 -> (hashSHA384, g')
       4 -> (hashSHA512, g')
       _ -> error "World broken"

showBytes :: BSL.ByteString -> String
showBytes bs = go (BSL.unpack bs)
 where
  go [] = ""
  go (x:rest)
   | x < 0x10  = "0" ++ showHex x "" ++ go rest
   | otherwise = showHex x "" ++ go rest

genCase :: CryptoRandomGen g => g -> Int -> (Map String String, g)
genCase g0 size =
  let (pub, priv, g1) = keyPair g0 size
      (msg, g2)       = message g1
      (hashi, g3)     = hash g2
      hashfun         = hashFunction hashi
      hashlen         = 8 * BSL.length (hashfun BSL.empty)
      n               = public_n pub
      d               = private_d priv
      esig            = rsassa_pkcs1_v1_5_sign hashi priv msg
      ecipher         = encryptOAEP g3 hashfun (generateMGF1 hashfun)
                                    BSL.empty pub msg
  in case (esig, ecipher) of
       (Right sig, Right (cipher, g4)) ->
         (Map.fromList [("d", Numeric.showHex d ""),
                        ("n", Numeric.showHex n ""),
                        ("k", Numeric.showHex (computeK n) ""),
                        ("u", Numeric.showHex (barrett n) "" ),
                        ("h", show hashlen),
                        ("m", showBytes msg),
                        ("s", showBytes sig),
                        ("c", showBytes cipher)], g4)
       (Left _, Right (_, g4)) ->
         genCase g4 size
       (_, _) ->
         genCase g3 size

base :: Integer
base = 2 ^ (64 :: Integer)

barrett :: Integer -> Integer
barrett m = (base ^ (2 * k)) `div` m
 where
  k = computeK m

computeK :: Integer -> Int
computeK v = go 0 1
 where
  go k acc | v <= acc  = k
           | otherwise = go (k + 1) (acc * base)

go :: CryptoRandomGen g => g -> Handle -> Int -> Int -> IO ()
go _ _ _ 0 = return ()
go g hndl size count =
  do let (map, g') = genCase g size
     forM_ (Map.toList map) $ \ (key, val) ->
       do hPutStr   hndl key
          hPutStr   hndl ": "
          hPutStrLn hndl val
     putStr "."
     hFlush stdout
     go g' hndl size (count - 1)

main :: IO ()
main =
  forM_ [512,1024,2048,3072,4096,8192,15360] $ \ size ->
    withFile ("rsa" ++ show size ++ ".test") WriteMode $ \ hndl ->
      do gen <- newGenIO :: IO SystemRandom
         putStr ("Generating " ++ show size ++ "-bit test cases ")
         go gen hndl size 750
         putStrLn " done."

