75 lines
2.6 KiB
Haskell
75 lines
2.6 KiB
Haskell
{-# LANGUAGE DeriveGeneric #-}
|
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
|
{-# LANGUAGE JavaScriptFFI #-}
|
|
|
|
{-| This module provides implementations of cryptographic utilities that only
|
|
work for GHCJS
|
|
-}
|
|
|
|
module Dhall.Crypto (
|
|
SHA256Digest(..)
|
|
, sha256DigestFromByteString
|
|
, sha256Hash
|
|
) where
|
|
|
|
import Control.DeepSeq (NFData)
|
|
import Data.ByteArray (ByteArrayAccess)
|
|
import Data.ByteArray.Encoding (Base(Base16), convertToBase)
|
|
import Data.ByteString (ByteString)
|
|
import GHC.Generics (Generic)
|
|
import JavaScript.TypedArray.ArrayBuffer (ArrayBuffer)
|
|
import System.IO.Unsafe (unsafePerformIO)
|
|
|
|
import qualified Data.ByteString as ByteString
|
|
import qualified Data.ByteString.Char8 as ByteString.Char8
|
|
import qualified GHCJS.Buffer as Buffer
|
|
|
|
-- | A SHA256 digest
|
|
newtype SHA256Digest = SHA256Digest { unSHA256Digest :: ByteString }
|
|
deriving (Eq, Generic, Ord, NFData, ByteArrayAccess)
|
|
|
|
instance Show SHA256Digest where
|
|
show (SHA256Digest bytes) = ByteString.Char8.unpack $ convertToBase Base16 bytes
|
|
|
|
{-| Attempt to interpret a `ByteString` as a `SHA256Digest`, returning
|
|
`Nothing` if the conversion fails
|
|
-}
|
|
sha256DigestFromByteString :: ByteString -> Maybe SHA256Digest
|
|
sha256DigestFromByteString bytes
|
|
| ByteString.length bytes == 32 = Just $ SHA256Digest bytes
|
|
| otherwise = Nothing
|
|
|
|
-- Use NodeJS' crypto module if there's a 'process' module, e.g. we're running
|
|
-- inside GHCJS' THRunner. If we're running in the browser, use the WebCrypto
|
|
-- interface.
|
|
foreign import javascript interruptible
|
|
"if (typeof process === 'undefined') { \
|
|
\ crypto.subtle.digest('SHA-256', $1).then($c) \
|
|
\} else { \
|
|
\ $c(require('crypto').createHash('sha256').update(Buffer.from($1)).digest().buffer) \
|
|
\}"
|
|
js_sha256Hash :: ArrayBuffer -> IO ArrayBuffer
|
|
|
|
byteStringToArrayBuffer :: ByteString -> ArrayBuffer
|
|
byteStringToArrayBuffer b =
|
|
js_arrayBufferSlice offset len $ Buffer.getArrayBuffer buffer
|
|
where
|
|
(buffer, offset, len) = Buffer.fromByteString b
|
|
|
|
foreign import javascript unsafe "$3.slice($1, $1 + $2)"
|
|
js_arrayBufferSlice :: Int -> Int -> ArrayBuffer -> ArrayBuffer
|
|
|
|
arrayBufferToByteString :: ArrayBuffer -> ByteString
|
|
arrayBufferToByteString =
|
|
Buffer.toByteString 0 Nothing . Buffer.createFromArrayBuffer
|
|
|
|
-- | Hash a `ByteString` and return the hash as a `SHA256Digest`
|
|
sha256Hash :: ByteString -> SHA256Digest
|
|
sha256Hash bytes
|
|
| ByteString.length out == 32 = SHA256Digest out
|
|
| otherwise = error "sha256Hash: didn't produce 32 bytes"
|
|
where
|
|
out =
|
|
arrayBufferToByteString $
|
|
unsafePerformIO $ js_sha256Hash (byteStringToArrayBuffer bytes)
|