packages feed

hashes-0.2.3: src/Data/Hash/Internal/OpenSSL.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}

#include <openssl/opensslv.h>

-- |
-- Module: Data.Hash.Internal.OpenSSL
-- Copyright: Copyright © 2021 Lars Kuhtz <lakuhtz@gmail.com>
-- License: MIT
-- Maintainer: Lars Kuhtz <lakuhtz@gmail.com>
-- Stability: experimental
--
-- Bindings for OpenSSL EVP Message Digest Routines
--
module Data.Hash.Internal.OpenSSL
(

-- * EVP digest routines

  Algorithm(..)
, Ctx(..)
, Digest(..)
, resetCtx
, initCtx
, updateCtx
, finalCtx

-- * Algorithms

, OpenSslDigest(..)
, OpenSslException(..)

-- ** SHA2
--
-- $sha2

, Sha2_224(..)
, Sha2_256(..)
, Sha2_384(..)
, Sha2_512(..)
, Sha2_512_224(..)
, Sha2_512_256(..)

-- ** SHA3
--
-- $sha3

, Sha3_224(..)
, Sha3_256(..)
, Sha3_384(..)
, Sha3_512(..)
, Shake128(..)
, Shake256(..)

-- ** Keccak
--
-- $keccak

, Keccak256(..)
, Keccak512(..)

-- *** Unsafe finalize functions
, finalizeKeccak256Ptr
, finalizeKeccak512Ptr

-- ** Blake2
--
-- $blake2

, Blake2b512(..)
, Blake2s256(..)
) where

import Control.Exception
import Control.Monad

import qualified Data.ByteString.Short as BS
import Data.Void
import Data.Word

import Foreign.ForeignPtr
import Foreign.Marshal
import Foreign.Ptr

import GHC.IO

-- internal modules

import Data.Hash.Class.Mutable
import Data.Hash.Internal.Utils


#if OPENSSL_VERSION_NUMBER < 0x10100000L
#error "Unsupported OpenSSL version. Please install OpenSSL >= 1.1.0"
#endif

-- -------------------------------------------------------------------------- --
-- OpenSSL Message Digests

newtype OpenSslException = OpenSslException String
    deriving (Show)

instance Exception OpenSslException

newtype Algorithm = Algorithm (Ptr Void)
newtype Ctx a = Ctx (ForeignPtr Void)
newtype Digest a = Digest BS.ShortByteString
    deriving (Eq, Ord)
    deriving (Show) via B16ShortByteString

#if OPENSSL_VERSION_NUMBER >= 0x10100000L
foreign import ccall unsafe "openssl/evp.h EVP_MD_CTX_new"
#else
foreign import ccall unsafe "openssl/evp.h EVP_MD_CTX_create"
#endif
    c_evp_ctx_new :: IO (Ptr a)

#if OPENSSL_VERSION_NUMBER >= 0x10100000L
foreign import ccall unsafe "openssl/evp.h &EVP_MD_CTX_free"
#else
foreign import ccall unsafe "openssl/evp.h &EVP_MD_CTX_destroy"
#endif
    c_evp_ctx_free_ptr :: FunPtr (Ptr a -> IO ())

#if OPENSSL_VERSION_NUMBER >= 0x30000000L
foreign import ccall unsafe "openssl/evp.h EVP_DigestInit_ex2"
#else
foreign import ccall unsafe "openssl/evp.h EVP_DigestInit_ex"
#endif
    c_evp_digest_init :: Ptr ctx -> Ptr alg -> Ptr Void {- nullPtr -} -> IO Bool

foreign import ccall unsafe "openssl/evp.h EVP_DigestUpdate"
    c_evp_digest_update :: Ptr ctx -> Ptr d -> Int -> IO Bool

foreign import ccall unsafe "openssl/evp.h EVP_DigestFinal_ex"
    c_evp_digest_final :: Ptr ctx -> Ptr d -> Int -> IO Bool

#if OPENSSL_VERSION_NUMBER >= 0x30000000L
foreign import ccall unsafe "openssl/evp.h EVP_MD_CTX_get0_md"
#else
foreign import ccall unsafe "openssl/evp.h EVP_MD_CTX_md"
#endif
    c_evp_ctx_get0_md :: Ptr ctx -> IO Algorithm

#if OPENSSL_VERSION_NUMBER >= 0x30000000L
foreign import ccall unsafe "openssl/evp.h EVP_MD_get_size"
#else
foreign import ccall unsafe "openssl/evp.h EVP_MD_size"
#endif
    c_evp_get_size :: Algorithm -> IO Int

-- | Allocates and initializes a new context. The context may be reused by
-- calling 'resetCtx' on it.
--
initCtx :: Algorithm -> IO (Ctx a)
initCtx (Algorithm alg) = do
    ctx <- mask_ $ do
        ptr <- c_evp_ctx_new
        when (ptr == nullPtr) $ throw $ OpenSslException "failed to create new context"
        newForeignPtr c_evp_ctx_free_ptr ptr
    r <- withForeignPtr ctx $ \ptr ->
        c_evp_digest_init ptr alg nullPtr
    unless r $ throw $ OpenSslException "digest initialization failed"
    return $ Ctx ctx
{-# INLINE initCtx #-}

-- | Resets a context an initialize context.
--
resetCtx :: Ctx a -> IO ()
resetCtx (Ctx ctx) = do
    r <- withForeignPtr ctx $ \ptr ->
        c_evp_digest_init ptr nullPtr nullPtr
    unless r $ throw $ OpenSslException "digest re-initialization failed"
{-# INLINE resetCtx #-}

-- | Feed more data into an context.
--
updateCtx :: Ctx a -> Ptr Word8 -> Int -> IO ()
updateCtx (Ctx ctx) d c = withForeignPtr ctx $ \ptr -> do
    r <- c_evp_digest_update ptr d c
    unless r $ throw $ OpenSslException "digest update failed"
{-# INLINE updateCtx #-}

-- | Finalize a hash and return the digest.
--
finalCtx :: Ctx a -> IO (Digest a)
finalCtx (Ctx ctx) = withForeignPtr ctx $ \ptr -> do
    s <- c_evp_ctx_get0_md ptr >>= c_evp_get_size
    allocaBytes s $ \dptr -> do
        r <- c_evp_digest_final ptr dptr 0
        unless r $ throw $ OpenSslException "digest finalization failed"
        Digest <$> BS.packCStringLen (dptr, s)
{-# INLINE finalCtx #-}

-- -------------------------------------------------------------------------- --
-- Support for DerivingVia

class OpenSslDigest a where
    algorithm :: Algorithm

instance OpenSslDigest a => IncrementalHash (Digest a) where
    type Context (Digest a) = Ctx a
    update = updateCtx
    finalize = finalCtx
    {-# INLINE update #-}
    {-# INLINE finalize #-}

instance OpenSslDigest a => Hash (Digest a) where
    initialize = initCtx (algorithm @a)
    {-# INLINE initialize #-}

instance OpenSslDigest a => ResetableHash (Digest a) where
    reset = resetCtx
    {-# INLINE reset #-}

-- -------------------------------------------------------------------------- --
-- Digests
-- -------------------------------------------------------------------------- --

-- -------------------------------------------------------------------------- --
-- SHA-2

-- $sha2
--
-- SHA-2 (Secure Hash Algorithm 2) is a family of cryptographic hash functions
-- standardized in NIST FIPS 180-4, first published in 2001. These functions
-- conform to NIST FIPS 180-4.
--
-- The following hash functions from the SHA-2 family are supported in
-- openssl-3.0 (cf. https://www.openssl.org/docs/man3.0/man3/EVP_sha224.html)
--
-- EVP_sha224, EVP_sha256, EVP_sha512_224, EVP_sha512_256, EVP_sha384,
-- EVP_sha512
--

foreign import ccall unsafe "openssl/evp.h EVP_sha224"
    c_evp_sha2_224 :: Algorithm

foreign import ccall unsafe "openssl/evp.h EVP_sha256"
    c_evp_sha2_256 :: Algorithm

foreign import ccall unsafe "openssl/evp.h EVP_sha384"
    c_evp_sha2_384 :: Algorithm

foreign import ccall unsafe "openssl/evp.h EVP_sha512"
    c_evp_sha2_512 :: Algorithm

foreign import ccall unsafe "openssl/evp.h EVP_sha512_224"
    c_evp_sha2_512_224 :: Algorithm

foreign import ccall unsafe "openssl/evp.h EVP_sha512_256"
    c_evp_sha2_512_256 :: Algorithm

newtype Sha2_224 = Sha2_224 BS.ShortByteString
    deriving (Eq, Ord)
    deriving (Show) via B16ShortByteString
    deriving (IncrementalHash, Hash) via (Digest Sha2_224)
instance OpenSslDigest Sha2_224 where algorithm = c_evp_sha2_224

newtype Sha2_256 = Sha2_256 BS.ShortByteString
    deriving (Eq, Ord)
    deriving (Show) via B16ShortByteString
    deriving (IncrementalHash, Hash) via (Digest Sha2_256)
instance OpenSslDigest Sha2_256 where algorithm = c_evp_sha2_256

newtype Sha2_384 = Sha2_384 BS.ShortByteString
    deriving (Eq, Ord)
    deriving (Show) via B16ShortByteString
    deriving (IncrementalHash, Hash) via (Digest Sha2_384)
instance OpenSslDigest Sha2_384 where algorithm = c_evp_sha2_384

newtype Sha2_512 = Sha2_512 BS.ShortByteString
    deriving (Eq, Ord)
    deriving (Show) via B16ShortByteString
    deriving (IncrementalHash, Hash) via (Digest Sha2_512)
instance OpenSslDigest Sha2_512 where algorithm = c_evp_sha2_512

newtype Sha2_512_224 = Sha2_512_224 BS.ShortByteString
    deriving (Eq, Ord)
    deriving (Show) via B16ShortByteString
    deriving (IncrementalHash, Hash) via (Digest Sha2_512_224)
instance OpenSslDigest Sha2_512_224 where algorithm = c_evp_sha2_512_224

newtype Sha2_512_256 = Sha2_512_256 BS.ShortByteString
    deriving (Eq, Ord)
    deriving (Show) via B16ShortByteString
    deriving (IncrementalHash, Hash) via (Digest Sha2_512_256)
instance OpenSslDigest Sha2_512_256 where algorithm = c_evp_sha2_512_256

-- -------------------------------------------------------------------------- --
-- SHA-3

-- $sha3
--
-- SHA-3 (Secure Hash Algorithm 3) is a family of cryptographic hash functions
-- standardized in NIST FIPS 202, first published in 2015. It is based on the
-- Keccak algorithm. These functions conform to NIST FIPS 202.
--
-- The following hash functions from the SHA-3 family are supported in
-- openssl-3.0 (cf. https://www.openssl.org/docs/man3.0/man3/EVP_sha3_224.html)
--
-- EVP_sha3_224, EVP_sha3_256, EVP_sha3_384, EVP_sha3_512, EVP_shake128,
-- EVP_shake256

foreign import ccall unsafe "openssl/evp.h EVP_sha3_224"
    c_evp_sha3_224 :: Algorithm

foreign import ccall unsafe "openssl/evp.h EVP_sha3_256"
    c_evp_sha3_256 :: Algorithm

foreign import ccall unsafe "openssl/evp.h EVP_sha3_384"
    c_evp_sha3_384 :: Algorithm

foreign import ccall unsafe "openssl/evp.h EVP_sha3_512"
    c_evp_sha3_512 :: Algorithm

foreign import ccall unsafe "openssl/evp.h EVP_shake128"
    c_evp_shake128 :: Algorithm

foreign import ccall unsafe "openssl/evp.h EVP_shake256"
    c_evp_shake256 :: Algorithm

newtype Sha3_224 = Sha3_224 BS.ShortByteString
    deriving (Eq, Ord)
    deriving (Show) via B16ShortByteString
    deriving (IncrementalHash, Hash) via (Digest Sha3_224)
instance OpenSslDigest Sha3_224 where algorithm = c_evp_sha3_224

newtype Sha3_256 = Sha3_256 BS.ShortByteString
    deriving (Eq, Ord)
    deriving (Show) via B16ShortByteString
    deriving (IncrementalHash, Hash) via (Digest Sha3_256)
instance OpenSslDigest Sha3_256 where algorithm = c_evp_sha3_256

newtype Sha3_384 = Sha3_384 BS.ShortByteString
    deriving (Eq, Ord)
    deriving (Show) via B16ShortByteString
    deriving (IncrementalHash, Hash) via (Digest Sha3_384)
instance OpenSslDigest Sha3_384 where algorithm = c_evp_sha3_384

newtype Sha3_512 = Sha3_512 BS.ShortByteString
    deriving (Eq, Ord)
    deriving (Show) via B16ShortByteString
    deriving (IncrementalHash, Hash) via (Digest Sha3_512)
instance OpenSslDigest Sha3_512 where algorithm = c_evp_sha3_512

newtype Shake128 = Shake128 BS.ShortByteString
    deriving (Eq, Ord)
    deriving (Show) via B16ShortByteString
    deriving (IncrementalHash, Hash) via (Digest Shake128)
instance OpenSslDigest Shake128 where algorithm = c_evp_shake128

newtype Shake256 = Shake256 BS.ShortByteString
    deriving (Eq, Ord)
    deriving (Show) via B16ShortByteString
    deriving (IncrementalHash, Hash) via (Digest Shake256)
instance OpenSslDigest Shake256 where algorithm = c_evp_shake256

-- -------------------------------------------------------------------------- --
-- Keccak

-- $keccak
--
-- This is the latest version of Keccak-256 hash function that was submitted to
-- the SHA3 competition. It is different from the final NIST SHA3 hash.
--
-- The difference between NIST SHA3-256 and Keccak-256 is the use of a different
-- padding character for the input message. The former uses '0x06' and the
-- latter uses '0x01'.
--
-- This version of Keccak-256 is used by the Ethereum project.
--
-- This implementation of Keccak-256 uses internal OpenSSL APIs. It may break
-- with new versions of OpenSSL. It may also be broken for existing versions of
-- OpenSSL. Portability of the code is unknown.
--
-- ONLY USE THIS CODE AFTER YOU HAVE VERIFIED THAT IT WORKS WITH OUR VERSION OF
-- OPENSSL.
--
-- For details see the file cbits/keccak.c.

-- KECCAK-256

newtype Keccak256 = Keccak256 BS.ShortByteString
    deriving (Eq, Ord)
    deriving (Show) via B16ShortByteString

foreign import ccall unsafe "keccak.h keccak256_newctx"
    c_keccak256_newctx :: IO (Ptr ctx)

foreign import ccall unsafe "keccak.h keccak256_init"
    c_keccak256_init :: Ptr ctx -> IO Bool

foreign import ccall unsafe "keccak.h keccak256_reset"
    c_keccak256_reset :: Ptr ctx -> IO Bool

foreign import ccall unsafe "keccak.h keccak256_update"
    c_keccak256_update :: Ptr ctx -> Ptr Word8 -> Int -> IO Bool

foreign import ccall unsafe "keccak.h keccak256_final"
    c_keccak256_final :: Ptr ctx -> Ptr Word8 -> IO Bool

foreign import ccall unsafe "keccak.h &keccak256_freectx"
    c_keccak256_freectx_ptr :: FunPtr (Ptr ctx -> IO ())

instance IncrementalHash Keccak256 where
    type Context Keccak256 = Ctx Keccak256
    update (Ctx ctx) ptr n = withForeignPtr ctx $ \cptr -> do
        r <- c_keccak256_update cptr ptr n
        unless r $ throw $ OpenSslException "digest update failed"
    finalize (Ctx ctx) = withForeignPtr ctx $ \cptr -> do
        allocaBytes 32 $ \dptr -> do
            r <- c_keccak256_final cptr dptr
            unless r $ throw $ OpenSslException "digest finalization failed"
            Keccak256 <$> BS.packCStringLen (castPtr dptr, 32)
    {-# INLINE update #-}
    {-# INLINE finalize #-}

newKeccak256Ctx :: IO (Ctx Keccak256)
newKeccak256Ctx = fmap Ctx $ mask_ $ do
    ptr <- c_keccak256_newctx
    when (ptr == nullPtr) $ throw $ OpenSslException "failed to initialize context"
    newForeignPtr c_keccak256_freectx_ptr ptr
{-# INLINE newKeccak256Ctx #-}

resetKeccak256Ctx :: Ctx Keccak256 -> IO ()
resetKeccak256Ctx (Ctx ctx) = do
    r <- withForeignPtr ctx $ \ptr ->
        c_keccak256_reset ptr
    unless r $ throw $ OpenSslException "digest re-initialization failed"
{-# INLINE resetKeccak256Ctx #-}

-- | Low-Level function that writes the final digest directly into the provided
-- pointer. The pointer must point to at least 64 bytes of allocated memory.
-- This is not checked and a violation of this condition may result in a
-- segmentation fault.
--
finalizeKeccak256Ptr :: Ctx Keccak256 -> Ptr Word8 -> IO ()
finalizeKeccak256Ptr (Ctx ctx) dptr =
    withForeignPtr ctx $ \cptr -> do
        r <- c_keccak256_final cptr dptr
        unless r $ throw $ OpenSslException "digest finalization failed"
{-# INLINE finalizeKeccak256Ptr #-}

instance Hash Keccak256 where
    initialize = do
        Ctx ctx <- newKeccak256Ctx
        r <- withForeignPtr ctx $ \ptr ->
            c_keccak256_init ptr
        unless r $ throw $ OpenSslException "digest initialization failed"
        return $ Ctx ctx
    {-# INLINE initialize #-}

instance ResetableHash Keccak256 where
    reset = resetKeccak256Ctx
    {-# INLINE reset #-}

-- KECCAK-512

newtype Keccak512 = Keccak512 BS.ShortByteString
    deriving (Eq, Ord)
    deriving (Show) via B16ShortByteString

foreign import ccall unsafe "keccak.h keccak512_newctx"
    c_keccak512_newctx :: IO (Ptr ctx)

foreign import ccall unsafe "keccak.h keccak512_init"
    c_keccak512_init :: Ptr ctx -> IO Bool

foreign import ccall unsafe "keccak.h keccak512_reset"
    c_keccak512_reset :: Ptr ctx -> IO Bool

foreign import ccall unsafe "keccak.h keccak512_update"
    c_keccak512_update :: Ptr ctx -> Ptr Word8 -> Int -> IO Bool

foreign import ccall unsafe "keccak.h keccak512_final"
    c_keccak512_final :: Ptr ctx -> Ptr Word8 -> IO Bool

foreign import ccall unsafe "keccak.h &keccak512_freectx"
    c_keccak512_freectx_ptr :: FunPtr (Ptr ctx -> IO ())

instance IncrementalHash Keccak512 where
    type Context Keccak512 = Ctx Keccak512
    update (Ctx ctx) ptr n = withForeignPtr ctx $ \cptr -> do
        r <- c_keccak512_update cptr ptr n
        unless r $ throw $ OpenSslException "digest update failed"
    finalize (Ctx ctx) = withForeignPtr ctx $ \cptr -> do
        allocaBytes 64 $ \dptr -> do
            r <- c_keccak512_final cptr dptr
            unless r $ throw $ OpenSslException "digest finalization failed"
            Keccak512 <$> BS.packCStringLen (castPtr dptr, 64)
    {-# INLINE update #-}
    {-# INLINE finalize #-}

newKeccak512Ctx :: IO (Ctx Keccak512)
newKeccak512Ctx = fmap Ctx $ mask_ $ do
    ptr <- c_keccak512_newctx
    when (ptr == nullPtr) $ throw $ OpenSslException "failed to initialize context"
    newForeignPtr c_keccak512_freectx_ptr ptr
{-# INLINE newKeccak512Ctx #-}

resetKeccak512Ctx :: Ctx Keccak512 -> IO ()
resetKeccak512Ctx (Ctx ctx) = do
    r <- withForeignPtr ctx $ \ptr ->
        c_keccak512_reset ptr
    unless r $ throw $ OpenSslException "digest re-initialization failed"
{-# INLINE resetKeccak512Ctx #-}

-- | Low-Level function that writes the final digest directly into the provided
-- pointer. The pointer must point to at least 64 bytes of allocated memory.
-- This is not checked and a violation of this condition may result in a
-- segmentation fault.
--
finalizeKeccak512Ptr :: Ctx Keccak512 -> Ptr Word8 -> IO ()
finalizeKeccak512Ptr (Ctx ctx) dptr = do
    withForeignPtr ctx $ \cptr -> do
        r <- c_keccak512_final cptr dptr
        unless r $ throw $ OpenSslException "digest finalization failed"
{-# INLINE finalizeKeccak512Ptr #-}

instance Hash Keccak512 where
    initialize = do
        Ctx ctx <- newKeccak512Ctx
        r <- withForeignPtr ctx $ \ptr ->
            c_keccak512_init ptr
        unless r $ throw $ OpenSslException "digest initialization failed"
        return $ Ctx ctx
    {-# INLINE initialize #-}

instance ResetableHash Keccak512 where
    reset = resetKeccak512Ctx
    {-# INLINE reset #-}

-- -------------------------------------------------------------------------- --
-- Blake

-- $blake2
--
-- BLAKE2 is an improved version of BLAKE, which was submitted to the NIST SHA-3
-- algorithm competition. The BLAKE2s and BLAKE2b algorithms are described in
-- RFC 7693.
--
-- The following hash functions from the BLAKE2 family are supported in
-- openssl-3.0 (cf.
-- https://www.openssl.org/docs/man3.0/man3/EVP_blake2b512.html)
--
-- EVP_blake2b512, EVP_blake2s256
--
-- While the BLAKE2b and BLAKE2s algorithms supports a variable length digest,
-- this implementation outputs a digest of a fixed length (the maximum length
-- supported), which is 512-bits for BLAKE2b and 256-bits for BLAKE2s.
--
--

foreign import ccall unsafe "openssl/evp.h EVP_blake2b512"
    c_evp_blake2b512 :: Algorithm

foreign import ccall unsafe "openssl/evp.h EVP_blake2s256"
    c_evp_blake2s256 :: Algorithm

newtype Blake2b512 = Blake2b512 BS.ShortByteString
    deriving (Eq, Ord)
    deriving (Show) via B16ShortByteString
    deriving (IncrementalHash, Hash) via (Digest Blake2b512)
instance OpenSslDigest Blake2b512 where algorithm = c_evp_blake2b512

newtype Blake2s256 = Blake2s256 BS.ShortByteString
    deriving (Eq, Ord)
    deriving (Show) via B16ShortByteString
    deriving (IncrementalHash, Hash) via (Digest Blake2s256)
instance OpenSslDigest Blake2s256 where algorithm = c_evp_blake2s256