packages feed

HsOpenSSL-0.2: OpenSSL/BN.hsc

#include "HsOpenSSL.h"

module OpenSSL.BN
    ( BigNum
    , BIGNUM

    , allocaBN
    , withBN
    , peekBN
    , newBN

#ifdef __GLASGOW_HASKELL__
    , integerToBN
    , bnToInteger
#endif
    )
    where

import           Control.Exception
import           Foreign


#ifndef __GLASGOW_HASKELL__
import           Control.Monad
import           Foreign.C
import           OpenSSL.Utils
#else
import           Foreign.C.Types
import           Data.Word (Word32)
import           GHC.Base
import           GHC.Num
import           GHC.Prim
import           GHC.IOBase (IO(..))
#endif

type BigNum = Ptr BIGNUM
data BIGNUM = BIGNUM


foreign import ccall unsafe "BN_new"
        _new :: IO BigNum

foreign import ccall unsafe "BN_free"
        _free :: BigNum -> IO ()


allocaBN :: (BigNum -> IO a) -> IO a
allocaBN m
    = bracket _new _free m


#ifndef __GLASGOW_HASKELL__

{- slow, safe functions ----------------------------------------------------- -}

foreign import ccall unsafe "BN_bn2dec"
        _bn2dec :: BigNum -> IO CString

foreign import ccall unsafe "BN_dec2bn"
        _dec2bn :: Ptr BigNum -> CString -> IO Int

foreign import ccall unsafe "HsOpenSSL_OPENSSL_free"
        _openssl_free :: Ptr a -> IO ()

withBN :: Integer -> (BigNum -> IO a) -> IO a
withBN dec m
    = withCString (show dec) $ \ strPtr ->
      alloca $ \ bnPtr ->
      do poke bnPtr nullPtr
         _dec2bn bnPtr strPtr
              >>= failIf (== 0)
         bracket (peek bnPtr) _free m


peekBN :: BigNum -> IO Integer
peekBN bn
    = do strPtr <- _bn2dec bn
         when (strPtr == nullPtr) $ fail "BN_bn2dec failed"
         str <- peekCString strPtr
         _openssl_free strPtr

         return $ read str


-- | Return a new, alloced bignum
newBN :: Integer -> IO BigNum
newBN i = do
  withCString (show i) (\str -> do
    alloca (\bnptr -> do
      poke bnptr nullPtr
      _dec2bn bnptr str >>= failIf (== 0)
      peek bnptr))

#else

{- fast, dangerous functions ------------------------------------------------ -}

-- Both BN (the OpenSSL library) and GMP (used by GHC) use the same internal
-- representation for numbers: an array of words, least-significant first. Thus
-- we can move from Integer's to BIGNUMs very quickly: by copying in the worst
-- case and by just alloca'ing and pointing into the Integer in the fast case.
-- Note that, in the fast case, it's very important that any foreign function
-- calls be "unsafe", that is, they don't call back into Haskell. Otherwise the
-- GC could do nasty things to the data which we thought that we had a pointer
-- to

foreign import ccall unsafe "memcpy"
        _copy_in :: ByteArray## -> Ptr () -> CSize -> IO ()

foreign import ccall unsafe "memcpy"
        _copy_out :: Ptr () -> ByteArray## -> CSize -> IO ()

-- These are taken from Data.Binary's disabled fast Integer support
data ByteArray = BA  {-# UNPACK #-} !ByteArray##
data MBA       = MBA {-# UNPACK #-} !(MutableByteArray## RealWorld)

newByteArray :: Int## -> IO MBA
newByteArray sz = IO $ \s ->
  case newByteArray## sz s of { (## s', arr ##) ->
  (## s', MBA arr ##) }

freezeByteArray :: MutableByteArray## RealWorld -> IO ByteArray
freezeByteArray arr = IO $ \s ->
  case unsafeFreezeByteArray## arr s of { (## s', arr' ##) ->
  (## s', BA arr' ##) }

-- | Convert a BIGNUM to an Integer
bnToInteger :: BigNum -> IO Integer
bnToInteger bn = do
  nlimbs <- (#peek BIGNUM, top) bn :: IO CSize
  case nlimbs of
    0 -> return 0
    1 -> do (I## i) <- (#peek BIGNUM, d) bn >>= peek
            negative <- (#peek BIGNUM, neg) bn :: IO Word32
            if negative == 0
               then return $ S## i
               else return $ 0 - (S## i)
    otherwise -> do
      let (I## nlimbsi) = fromIntegral nlimbs
          (I## limbsize) = (#size unsigned long)
      (MBA arr) <- newByteArray (nlimbsi *## limbsize)
      (BA ba) <- freezeByteArray arr
      limbs <- (#peek BIGNUM, d) bn
      _copy_in ba limbs $ fromIntegral $ nlimbs * (#size unsigned long)
      negative <- (#peek BIGNUM, neg) bn :: IO Word32
      if negative == 0
         then return $ J## nlimbsi ba
         else return $ 0 - (J## nlimbsi ba)

-- | This is a GHC specific, fast conversion between Integers and OpenSSL
--   bignums. It returns a malloced BigNum.
integerToBN :: Integer -> IO BigNum
integerToBN (S## v) = do
  bnptr <- mallocBytes (#size BIGNUM)
  limbs <- malloc :: IO (Ptr Word32)
  poke limbs $ fromIntegral $ abs $ I## v
  (#poke BIGNUM, d) bnptr limbs
  -- This is needed to give GHC enough type information since #poke just
  -- uses an offset
  let one :: Word32
      one = 1
  (#poke BIGNUM, flags) bnptr one
  (#poke BIGNUM, top) bnptr one
  (#poke BIGNUM, dmax) bnptr one
  (#poke BIGNUM, neg) bnptr (if (I## v) < 0 then one else 0)
  return bnptr

integerToBN v@(J## nlimbs_ bytearray)
  | v >= 0 = do
      let nlimbs = (I## nlimbs_)
      bnptr <- mallocBytes (#size BIGNUM)
      limbs <- mallocBytes ((#size unsigned) * nlimbs)
      (#poke BIGNUM, d) bnptr limbs
      (#poke BIGNUM, flags) bnptr (1 :: Word32)
      _copy_out limbs bytearray (fromIntegral $ (#size unsigned) * nlimbs)
      (#poke BIGNUM, top) bnptr ((fromIntegral nlimbs) :: Word32)
      (#poke BIGNUM, dmax) bnptr ((fromIntegral nlimbs) :: Word32)
      (#poke BIGNUM, neg) bnptr (0 :: Word32)
      return bnptr
  | otherwise = do bnptr <- integerToBN (0-v)
                   (#poke BIGNUM, neg) bnptr (1 :: Word32)
                   return bnptr

-- TODO: we could make a function which doesn't even allocate BN data if we
-- wanted to be very fast and dangerout. The BIGNUM could point right into the
-- Integer's data. However, I'm not sure about the semantics of the GC; which
-- might move the Integer data around.

withBN :: Integer -> (BigNum -> IO a) -> IO a
withBN dec m = bracket (integerToBN dec) _free m

peekBN :: BigNum -> IO Integer
peekBN = bnToInteger

newBN = integerToBN

#endif