module Crypto.Signature ( signParams , signJSON , hmacSHA256 , signRaw , mkHexHash , signParams_ , signJSON_ , signRaw_ ) where import Crypto.Hash (Digest, SHA256) import Crypto.MAC (HMAC (..), hmac) import Data.Aeson (Value (..)) import Data.Byteable (toBytes) import qualified Data.ByteString.Char8 as B (ByteString, concat, empty, pack, unpack) import Data.CaseInsensitive (CI, mk) import qualified Data.HashMap.Lazy as LH (HashMap, toList) import Data.HexString (fromBytes, toText) import Data.List (sortOn) import Data.Scientific (Scientific, floatingOrInteger) import qualified Data.Text as T (Text, unpack) import Data.Text.Encoding (encodeUtf8) import qualified Data.Text.Lazy as LT (Text, toStrict, unpack) import qualified Data.Vector as V (Vector, toList) hmacSHA256 :: B.ByteString -> B.ByteString -> CI B.ByteString hmacSHA256 solt = mkHexHash (mkHmacSHA256Hash solt) mkHmacSHA256Hash :: B.ByteString -> B.ByteString -> Digest SHA256 mkHmacSHA256Hash solt = hmacGetDigest . hmac solt mkHexHash :: (B.ByteString -> Digest a) -> B.ByteString -> CI B.ByteString mkHexHash mkHash = mk . encodeUtf8 . toText . fromBytes . toBytes . mkHash sortAndJoinTextParams :: [(LT.Text, LT.Text)] -> B.ByteString sortAndJoinTextParams = join . sort where sort :: [(LT.Text, LT.Text)] -> [(LT.Text, LT.Text)] sort = sortOn (\(k, _) -> LT.unpack k) join :: [(LT.Text, LT.Text)] -> B.ByteString join ((k,v):xs) = B.concat [encodeUtf8 $ LT.toStrict k, encodeUtf8 $ LT.toStrict v, join xs] join [] = B.empty signParams_ :: (B.ByteString -> Digest a) -> [(LT.Text, LT.Text)] -> CI B.ByteString signParams_ mkHash = mkHexHash mkHash . sortAndJoinTextParams signParams :: B.ByteString -> [(LT.Text, LT.Text)] -> CI B.ByteString signParams solt = signParams_ (mkHmacSHA256Hash solt) sortAndJoinJSON :: Value -> B.ByteString sortAndJoinJSON = v2b where sortHashMap :: LH.HashMap T.Text Value -> [(T.Text, Value)] sortHashMap = sortOn (\(k, _) -> T.unpack k) . LH.toList joinList :: [(T.Text, Value)] -> B.ByteString joinList [] = B.empty joinList ((k, v):xs) = B.concat [encodeUtf8 k, v2b v, joinList xs] joinArray :: V.Vector Value -> B.ByteString joinArray = B.concat . map v2b . V.toList v2b :: Value -> B.ByteString v2b (Object v) = (joinList . sortHashMap) v v2b (Array v) = joinArray v v2b (String v) = encodeUtf8 v v2b (Number v) = B.pack $ showNumber v v2b (Bool True) = B.pack "true" v2b (Bool False) = B.pack "false" v2b Null = B.empty showNumber :: Scientific -> String showNumber v = case floatingOrInteger v of Left n -> show n Right n -> show n signJSON_ :: (B.ByteString -> Digest a) -> Value -> CI B.ByteString signJSON_ mkHash = mkHexHash mkHash . sortAndJoinJSON signJSON :: B.ByteString -> Value -> CI B.ByteString signJSON solt = signJSON_ (mkHmacSHA256Hash solt) sortAndJoinRawParams :: [(B.ByteString, B.ByteString)] -> B.ByteString sortAndJoinRawParams = join . sort where sort :: [(B.ByteString, B.ByteString)] -> [(B.ByteString, B.ByteString)] sort = sortOn (\(k, _) -> B.unpack k) join :: [(B.ByteString, B.ByteString)] -> B.ByteString join [] = B.empty join ((k,v):xs) = B.concat [k, v, join xs] signRaw_ :: (B.ByteString -> Digest a) -> [(B.ByteString, B.ByteString)] -> CI B.ByteString signRaw_ mkHash = mkHexHash mkHash . sortAndJoinRawParams signRaw :: B.ByteString -> [(B.ByteString, B.ByteString)] -> CI B.ByteString signRaw solt = signRaw_ (mkHmacSHA256Hash solt)