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?
CodePudding user response:
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
.
CodePudding user response:
Found a way to do it without unsafePerformIO
the trick is to use a case statement which only uses the return function in the Nothing
case. Here's the implementation:
mkRsaKey :: String -> IO (Maybe RsaKey)
mkRsaKey privateKey = do
someOpenSslKey <- readPrivateKey privateKey PwNone
let maybeOpenSslKey = toKeyPair someOpenSslKey
case maybeOpenSslKey of
Just key -> openSslKeyToRsaKey key
Nothing -> return Nothing