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

{-# OPTIONS_HADDOCK prune #-}

-- |An interface to asymmetric cipher keypair.


{-# LINE 8 "OpenSSL/EVP/PKey.hsc" #-}

module OpenSSL.EVP.PKey
    ( PublicKey(..)
    , KeyPair(..)
    , SomePublicKey
    , SomeKeyPair

    -- private
    , PKey(..)
    , EVP_PKEY
    , withPKeyPtr
    , withPKeyPtr'
    , wrapPKeyPtr
    , unsafePKeyToPtr
    , touchPKey
    )
    where

import           Data.Typeable
import           Data.Maybe
import           Foreign
import           Foreign.C
import           OpenSSL.DSA
import           OpenSSL.EVP.Digest hiding (digest)
import           OpenSSL.RSA
import           OpenSSL.Utils

-- VaguePKey is a ForeignPtr to EVP_PKEY, that is either public key or
-- a ker pair. We can't tell which at compile time.
newtype VaguePKey = VaguePKey (ForeignPtr EVP_PKEY)
data    EVP_PKEY

-- Instances of class PKey can be converted back and forth to
-- VaguePKey.
class PKey k where
    -- Wrap the key (i.g. RSA) into EVP_PKEY.
    toPKey        :: k -> IO VaguePKey

    -- Extract the concrete key from the EVP_PKEY. Returns Nothing if
    -- the type mismatches.
    fromPKey      :: VaguePKey -> IO (Maybe k)

    -- Do the same as EVP_PKEY_size().
    pkeySize      :: k -> Int

    -- Return the default digesting algorithm for the key.
    pkeyDefaultMD :: k -> IO Digest

-- |Instances of this class has at least public portion of a
-- keypair. They might or might not have the private key.
class (Eq k, Typeable k, PKey k) => PublicKey k where

    -- |Wrap an arbitrary public key into polymorphic type
    -- 'SomePublicKey'.
    fromPublicKey :: k -> SomePublicKey
    fromPublicKey = SomePublicKey

    -- |Cast from the polymorphic type 'SomePublicKey' to the concrete
    -- type. Return 'Nothing' if failed.
    toPublicKey :: SomePublicKey -> Maybe k
    toPublicKey (SomePublicKey pk) = cast pk

-- |Instances of this class has both of public and private portions of
-- a keypair.
class PublicKey a => KeyPair a where

    -- |Wrap an arbitrary keypair into polymorphic type 'SomeKeyPair'.
    fromKeyPair :: a -> SomeKeyPair
    fromKeyPair = SomeKeyPair

    -- |Cast from the polymorphic type 'SomeKeyPair' to the concrete
    -- type. Return 'Nothing' if failed.
    toKeyPair :: SomeKeyPair -> Maybe a
    toKeyPair (SomeKeyPair pk) = cast pk

-- Reconstruct the concrete public-key type from an EVP_PKEY.
withConcretePubKey :: VaguePKey -> (forall k. PublicKey k => k -> IO a) -> IO a
withConcretePubKey pk f
    = withPKeyPtr pk $ \ pkeyPtr ->
          do pkeyType <- ((\hsc_ptr -> peekByteOff hsc_ptr 0)) pkeyPtr :: IO CInt
{-# LINE 88 "OpenSSL/EVP/PKey.hsc" #-}
             case pkeyType of

{-# LINE 90 "OpenSSL/EVP/PKey.hsc" #-}
               (6)
{-# LINE 91 "OpenSSL/EVP/PKey.hsc" #-}
                   -> do rsaPtr   <- _get1_RSA pkeyPtr
                         Just rsa <- absorbRSAPtr rsaPtr
                         f (rsa :: RSAPubKey)

{-# LINE 95 "OpenSSL/EVP/PKey.hsc" #-}

{-# LINE 96 "OpenSSL/EVP/PKey.hsc" #-}
               (116)
{-# LINE 97 "OpenSSL/EVP/PKey.hsc" #-}
                   -> do dsaPtr   <- _get1_DSA pkeyPtr
                         Just dsa <- absorbDSAPtr dsaPtr
                         f (dsa :: DSAPubKey)

{-# LINE 101 "OpenSSL/EVP/PKey.hsc" #-}
               _   -> fail ("withConcretePubKey: unsupported EVP_PKEY type: " ++ show pkeyType)

-- Reconstruct the concrete keypair type from an EVP_PKEY.
withConcreteKeyPair :: VaguePKey -> (forall k. KeyPair k => k -> IO a) -> IO a
withConcreteKeyPair pk f
    = withPKeyPtr pk $ \ pkeyPtr ->
          do pkeyType <- ((\hsc_ptr -> peekByteOff hsc_ptr 0)) pkeyPtr :: IO CInt
{-# LINE 108 "OpenSSL/EVP/PKey.hsc" #-}
             case pkeyType of

{-# LINE 110 "OpenSSL/EVP/PKey.hsc" #-}
               (6)
{-# LINE 111 "OpenSSL/EVP/PKey.hsc" #-}
                   -> do rsaPtr   <- _get1_RSA pkeyPtr
                         Just rsa <- absorbRSAPtr rsaPtr
                         f (rsa :: RSAKeyPair)

{-# LINE 115 "OpenSSL/EVP/PKey.hsc" #-}

{-# LINE 116 "OpenSSL/EVP/PKey.hsc" #-}
               (116)
{-# LINE 117 "OpenSSL/EVP/PKey.hsc" #-}
                   -> do dsaPtr   <- _get1_DSA pkeyPtr
                         Just dsa <- absorbDSAPtr dsaPtr
                         f (dsa :: DSAKeyPair)

{-# LINE 121 "OpenSSL/EVP/PKey.hsc" #-}
               _   -> fail ("withConcreteKeyPair: unsupported EVP_PKEY type: " ++ show pkeyType)


-- |This is an opaque type to hold an arbitrary public key in it. The
-- actual key type can be safelly type-casted using 'toPublicKey'.
data SomePublicKey = forall k. PublicKey k => SomePublicKey !k
    deriving Typeable

instance Eq SomePublicKey where
    (SomePublicKey a) == (SomePublicKey b)
        = case cast b of
            Just c  -> a == c
            Nothing -> False  -- different types

instance PublicKey SomePublicKey where
    fromPublicKey = id
    toPublicKey   = Just

instance PKey SomePublicKey where
    toPKey        (SomePublicKey k) = toPKey k
    pkeySize      (SomePublicKey k) = pkeySize k
    pkeyDefaultMD (SomePublicKey k) = pkeyDefaultMD k
    fromPKey pk
        = withConcretePubKey pk (return . Just . SomePublicKey)


-- |This is an opaque type to hold an arbitrary keypair in it. The
-- actual key type can be safelly type-casted using 'toKeyPair'.
data SomeKeyPair = forall k. KeyPair k => SomeKeyPair !k
    deriving Typeable

instance Eq SomeKeyPair where
    (SomeKeyPair a) == (SomeKeyPair b)
        = case cast b of
            Just c  -> a == c
            Nothing -> False

instance PublicKey SomeKeyPair where
    -- Cast the keypair to a public key, hiding its private part.
    fromPublicKey (SomeKeyPair k)
        = SomePublicKey k

    -- It's impossible to cast a public key to a keypair.
    toPublicKey _ = Nothing

instance KeyPair SomeKeyPair where
    fromKeyPair = id
    toKeyPair   = Just

instance PKey SomeKeyPair where
    toPKey        (SomeKeyPair k) = toPKey k
    pkeySize      (SomeKeyPair k) = pkeySize k
    pkeyDefaultMD (SomeKeyPair k) = pkeyDefaultMD k
    fromPKey pk
        = withConcreteKeyPair pk (return . Just . SomeKeyPair)


foreign import ccall unsafe "EVP_PKEY_new"
        _pkey_new :: IO (Ptr EVP_PKEY)

foreign import ccall unsafe "&EVP_PKEY_free"
        _pkey_free :: FunPtr (Ptr EVP_PKEY -> IO ())


wrapPKeyPtr :: Ptr EVP_PKEY -> IO VaguePKey
wrapPKeyPtr
    = fmap VaguePKey . newForeignPtr _pkey_free


withPKeyPtr' :: PKey k => k -> (Ptr EVP_PKEY -> IO a) -> IO a
withPKeyPtr' k f = do pk <- toPKey k
                      withPKeyPtr pk f


withPKeyPtr :: VaguePKey -> (Ptr EVP_PKEY -> IO a) -> IO a
withPKeyPtr (VaguePKey pkey) = withForeignPtr pkey


unsafePKeyToPtr :: VaguePKey -> Ptr EVP_PKEY
unsafePKeyToPtr (VaguePKey pkey) = unsafeForeignPtrToPtr pkey


touchPKey :: VaguePKey -> IO ()
touchPKey (VaguePKey pkey) = touchForeignPtr pkey



{-# LINE 208 "OpenSSL/EVP/PKey.hsc" #-}
-- The resulting Ptr RSA must be freed by caller.
foreign import ccall unsafe "EVP_PKEY_get1_RSA"
        _get1_RSA :: Ptr EVP_PKEY -> IO (Ptr RSA)

foreign import ccall unsafe "EVP_PKEY_set1_RSA"
        _set1_RSA :: Ptr EVP_PKEY -> Ptr RSA -> IO CInt


rsaToPKey :: RSAKey k => k -> IO VaguePKey
rsaToPKey rsa
    = withRSAPtr rsa $ \ rsaPtr ->
      do pkeyPtr <- _pkey_new >>= failIfNull
         _set1_RSA pkeyPtr rsaPtr >>= failIf_ (/= 1)
         wrapPKeyPtr pkeyPtr

rsaFromPKey :: RSAKey k => VaguePKey -> IO (Maybe k)
rsaFromPKey pk
        = withPKeyPtr pk $ \ pkeyPtr ->
          do pkeyType <- ((\hsc_ptr -> peekByteOff hsc_ptr 0)) pkeyPtr :: IO CInt
{-# LINE 227 "OpenSSL/EVP/PKey.hsc" #-}
             case pkeyType of
               (6)
{-# LINE 229 "OpenSSL/EVP/PKey.hsc" #-}
                   -> _get1_RSA pkeyPtr >>= absorbRSAPtr
               _   -> return Nothing

instance PublicKey RSAPubKey
instance PKey RSAPubKey where
    toPKey          = rsaToPKey
    fromPKey        = rsaFromPKey
    pkeySize        = rsaSize
    pkeyDefaultMD _ = return . fromJust =<< getDigestByName "sha1"

instance KeyPair RSAKeyPair
instance PublicKey RSAKeyPair
instance PKey RSAKeyPair where
    toPKey          = rsaToPKey
    fromPKey        = rsaFromPKey
    pkeySize        = rsaSize
    pkeyDefaultMD _ = return . fromJust =<< getDigestByName "sha1"

{-# LINE 247 "OpenSSL/EVP/PKey.hsc" #-}



{-# LINE 250 "OpenSSL/EVP/PKey.hsc" #-}
foreign import ccall unsafe "EVP_PKEY_get1_DSA"
        _get1_DSA :: Ptr EVP_PKEY -> IO (Ptr DSA)

foreign import ccall unsafe "EVP_PKEY_set1_DSA"
        _set1_DSA :: Ptr EVP_PKEY -> Ptr DSA -> IO CInt

dsaToPKey :: DSAKey k => k -> IO VaguePKey
dsaToPKey dsa
    = withDSAPtr dsa $ \ dsaPtr ->
      do pkeyPtr <- _pkey_new >>= failIfNull
         _set1_DSA pkeyPtr dsaPtr >>= failIf_ (/= 1)
         wrapPKeyPtr pkeyPtr

dsaFromPKey :: DSAKey k => VaguePKey -> IO (Maybe k)
dsaFromPKey pk
        = withPKeyPtr pk $ \ pkeyPtr ->
          do pkeyType <- ((\hsc_ptr -> peekByteOff hsc_ptr 0)) pkeyPtr :: IO CInt
{-# LINE 267 "OpenSSL/EVP/PKey.hsc" #-}
             case pkeyType of
               (116)
{-# LINE 269 "OpenSSL/EVP/PKey.hsc" #-}
                   -> _get1_DSA pkeyPtr >>= absorbDSAPtr
               _   -> return Nothing

instance PublicKey DSAPubKey
instance PKey DSAPubKey where
    toPKey          = dsaToPKey
    fromPKey        = dsaFromPKey
    pkeySize        = dsaSize
    pkeyDefaultMD _ = return . fromJust =<< getDigestByName "dss1"

instance KeyPair DSAKeyPair
instance PublicKey DSAKeyPair
instance PKey DSAKeyPair where
    toPKey          = dsaToPKey
    fromPKey        = dsaFromPKey
    pkeySize        = dsaSize
    pkeyDefaultMD _ = return . fromJust =<< getDigestByName "dss1"