haskellmonadsfunction-signature

Reduce IO (Maybe (IO (Maybe a)) to IO (Maybe a)


I have a function that reads an Rsa key with the HsOpenSsl's readPrivateKey function unfortunately the signature of my function is this String -> IO (Maybe (IO Maybe RsaKey)). I need the PEM format and a Cryptonite.RSA key and I wrote the function mkRsaKey to make that from a string in PEM format.

Heres the code:

import qualified Crypto.PubKey.RSA as Rsa --from cryptonite
import OpenSSL.EVP.PKey -- from HsOpenSSL
import OpenSSL.PEM -- from HsOpenSSL
import OpenSSL.RSA -- from HsOpenSSL
import Prelude

data RsaKey = RsaKey
  { rsaKeyCryptoniteKey :: Rsa.PrivateKey,
    rsaKeyStringRepr :: String
  }
  deriving (Show)

openSslKeyToCryptoniteKey :: RSAKeyPair -> Maybe Rsa.PrivateKey
openSslKeyToCryptoniteKey key = do
  let d = rsaD key
  let p = rsaP key
  let q = rsaQ key
  let mdP = rsaDMP1 key
  let mdQ = rsaDMQ1 key
  let mqinv = rsaIQMP key
  let size = rsaSize key
  let n = rsaN key
  let e = rsaE key
  dP <- mdP
  dQ <- mdQ
  qinv <- mqinv

  let pub = Rsa.PublicKey size n e
  return $ Rsa.PrivateKey pub d p q dP dQ qinv

openSslKeyToRsaKey :: RSAKeyPair -> IO (Maybe RsaKey)
openSslKeyToRsaKey key = do
  stringRepr <- writePublicKey key
  let maybeCryptoKey = openSslKeyToCryptoniteKey key
  return $ do
    cryptoKey <- maybeCryptoKey
    return $ RsaKey cryptoKey stringRepr

mkRsaKey :: String -> IO (Maybe (IO (Maybe RsaKey)))
mkRsaKey privateKey = do
  someOpenSslKey <- readPrivateKey privateKey PwNone
  let openSslKey = toKeyPair someOpenSslKey
  return $ openSslKeyToRsaKey <$> openSslKey

Now as you can see the type signature is in my sense not optimal I would like to have IO (Maybe RsaKey). How can I achieve this?

EDIT:

I actually managed to do it but I'm using unsafePerformIO:

mkRsaKey :: String -> IO (Maybe RsaKey)
mkRsaKey privateKey = do
  someOpenSslKey <- readPrivateKey privateKey PwNone
  return $ do
    openSslKey <- toKeyPair someOpenSslKey
    unsafePerformIO (openSslKeyToRsaKey $ openSslKey)

As far as I know you should never use unsafePerformIO would there be some way to do this without it?


Solution

  • Nice discovery with case. This is definitely not a place where you should be using unsafePerformIO. Here's a more compact way, for fun.

    flattenMaybe :: (Monad m) => m (Maybe (m (Maybe a))) -> m (Maybe a)
    flattenMaybe m = m >>= fromMaybe (return Nothing)
    

    And for extra fun, the ability to flatten layers like this is a characteristic ability of monads; we're just using that ability on m (Maybe ...), also known as MaybeT. So we could also write it like this:

    flattenMaybe = runMaybeT . join . fmap MaybeT . MaybeT
    

    Doing the necessary wrapping/unwrapping to use join at MaybeT m (MaybeT m a) -> MaybeT m a.