{-# LINE 1 "OpenSSL/EVP/Seal.hsc" #-}
{- -*- haskell -*- -}
{-# LINE 2 "OpenSSL/EVP/Seal.hsc" #-}

-- |Asymmetric cipher decryption using encrypted symmetric key. This
-- is an opposite of "OpenSSL.EVP.Open".

module OpenSSL.EVP.Seal
    ( seal
    , sealBS
    , sealLBS
    )
    where

import           Control.Monad
import qualified Data.ByteString.Char8 as B8
import qualified Data.ByteString.Lazy.Char8 as L8
import           Foreign
import           Foreign.C
import           OpenSSL.EVP.Cipher hiding (cipher)
import           OpenSSL.EVP.PKey
import           OpenSSL.Utils


foreign import ccall unsafe "EVP_SealInit"
        _SealInit :: Ptr EVP_CIPHER_CTX
                  -> Cipher
                  -> Ptr (Ptr CChar)
                  -> Ptr Int
                  -> CString
                  -> Ptr (Ptr EVP_PKEY)
                  -> Int
                  -> IO Int


sealInit :: Cipher -> [PKey] -> IO (CipherCtx, [String], String)

sealInit _ []
    = fail "sealInit: at least one public key is required"

sealInit cipher pubKeys
    = do ctx <- newCtx
         
         -- 暗号化された共通鍵の配列が書き込まれる場所を作る。各共通鍵
         -- は最大で pkeySize の長さになる。
         encKeyBufs <- mapM mallocEncKeyBuf pubKeys

         -- encKeys は [Ptr a] なので、これを Ptr (Ptr CChar) にしなけ
         -- ればならない。
         encKeyBufsPtr <- newArray encKeyBufs

         -- 暗号化された共通鍵の各々の長さが書き込まれる場所を作る。
         encKeyBufsLenPtr <- mallocArray nKeys

         -- IV の書き込まれる場所を作る。
         ivPtr <- mallocArray (cipherIvLength cipher)

         -- [PKey] から Ptr (Ptr EVP_PKEY) を作る。後でそれぞれの
         -- PKey を touchForeignPtr する事を忘れてはならない。
         pubKeysPtr <- newArray $ map unsafePKeyToPtr pubKeys

         -- 確保した領域を解放する IO アクションを作って置く
         let cleanup = do mapM_ free encKeyBufs
                          free encKeyBufsPtr
                          free encKeyBufsLenPtr
                          free ivPtr
                          free pubKeysPtr
                          mapM_ touchPKey pubKeys

         -- いよいよ EVP_SealInit を呼ぶ。
         ret <- withCipherCtxPtr ctx $ \ ctxPtr ->
                _SealInit ctxPtr cipher encKeyBufsPtr encKeyBufsLenPtr ivPtr pubKeysPtr nKeys

         if ret == 0 then
             cleanup >> raiseOpenSSLError
           else
             do encKeysLen <- peekArray nKeys encKeyBufsLenPtr
                encKeys    <- mapM peekCStringLen $ zip encKeyBufs encKeysLen
                iv         <- peekCString ivPtr
                cleanup
                return (ctx, encKeys, iv)
    where
      nKeys :: Int
      nKeys = length pubKeys

      mallocEncKeyBuf :: Storable a => PKey -> IO (Ptr a)
      mallocEncKeyBuf pubKey
          = pkeySize pubKey >>= mallocArray

-- |@'seal'@ lazilly encrypts a stream of data. The input string
-- doesn't necessarily have to be finite.
seal :: Cipher        -- ^ symmetric cipher algorithm to use
     -> [PKey]        -- ^ A list of public keys to encrypt a
                      --   symmetric key. At least one public key must
                      --   be supplied. If two or more keys are given,
                      --   the symmetric key are encrypted by each
                      --   public keys so that any of the
                      --   corresponding private keys can decrypt the
                      --   message.
     -> String        -- ^ input string to encrypt
     -> IO (String, [String], String) -- ^ (encrypted string, list of
                                      --   encrypted asymmetric keys,
                                      --   IV)
seal cipher pubKeys input
    = do (output, encKeys, iv) <- sealLBS cipher pubKeys $ L8.pack input
         return (L8.unpack output, encKeys, iv)

-- |@'sealBS'@ strictly encrypts a chunk of data.
sealBS :: Cipher     -- ^ symmetric cipher algorithm to use
       -> [PKey]     -- ^ list of public keys to encrypt a symmetric
                     --   key
       -> B8.ByteString -- ^ input string to encrypt
       -> IO (B8.ByteString, [String], String) -- ^ (encrypted string,
                                            --   list of encrypted
                                            --   asymmetric keys, IV)
sealBS cipher pubKeys input
    = do (ctx, encKeys, iv) <- sealInit cipher pubKeys
         output             <- cipherStrictly ctx input
         return (output, encKeys, iv)

-- |@'sealLBS'@ lazilly encrypts a stream of data. The input string
-- doesn't necessarily have to be finite.
sealLBS :: Cipher         -- ^ symmetric cipher algorithm to use
        -> [PKey]         -- ^ list of public keys to encrypt a
                          --   symmetric key
        -> L8.ByteString -- ^ input string to encrypt
        -> IO (L8.ByteString, [String], String) -- ^ (encrypted
                                                 --   string, list of
                                                 --   encrypted
                                                 --   asymmetric keys,
                                                 --   IV)
sealLBS cipher pubKeys input
    = do (ctx, encKeys, iv) <- sealInit cipher pubKeys
         output             <- cipherLazily ctx input
         return (output, encKeys, iv)