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?
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.
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
Related
Please look at the following example:
import qualified Data.Map as Map
candidateDB :: Map.Map Int String
candidateDB = Map.fromList [(1,"candidate1")
,(2,"candidate2")
,(3,"candidate3")]
assessCandidateMaybe :: Int -> Maybe String
assessCandidateMaybe cId = do
candidate <- Map.lookup cId candidateDB
let passed = candidate ++ "x"
return passed
assessCandidateMaybe' cId = do
candidate <- cId
let passed = candidate ++ "x"
return passed
candidates = ["candidate1","candidate2","candidate3"]
main :: IO ()
main = do
print(assessCandidateMaybe 1)
print(assessCandidateMaybe' (Map.lookup 1 candidateDB))
print(assessCandidateMaybe' (Just "Box"))
print(assessCandidateMaybe' candidates)
In assessCandidateMaybe, I used Map.lookup. When I put the Map.lookup outside the assessCandidateMaybe function, thus creating assessCandidateMaybe', it became a monad.
assessCandidateMaybe' :: Monad m => m String -> m String
Now I can even pass a maybe String or list of Strings to it and it works.
$ runhaskell examples.hs
Just "candidate1x"
Just "candidate1x"
Just "Boxx"
["candidate1x","candidate2x","candidate3x"]
What is going on here?
I'm trying to write a Monad which renders some HTML, while tracking (and caching) a few specific function calls. Here's what I tried:
data TemplateM a = TemplateM
{ templateCache :: ![(Text, Text)]
, templateResult :: !(IO a)
}
Here's how I plan to use this:
renderCached :: Text -> TemplateM Text
renderCached k =
-- lookup templateCache from the monadic context, if it lacks the key,
-- then fetch the key from an external data source (which is where the
-- "IO interior" comes from, and store it in templateCache (monadic context)
Notably, I do not want arbitrary IO actions to be executed in TemplateM via lift, liftIO, and suchlike. The only IO that should happen in TemplateM is to fetch something from the cache via the renderCached function.
I was able to define the Functor and Applicative instances for this, but got completely stuck with the Monad instance. Here's how far I got:
instance Functor TemplateM where
{-# INLINE fmap #-}
fmap fn tmpl = tmpl{templateResult=fmap fn (templateResult tmpl)}
instance Applicative TemplateM where
{-# INLINE pure #-}
pure x = TemplateM
{ templateCache = []
, templateResult = pure x
}
{-# INLINE (<*>) #-}
fn <*> f =
let fnCache = templateCache fn
fnFunction = templateResult fn
fCache = templateCache f
fResult = templateResult f
in TemplateM { templateCache = fnCache <> fCache
, templateResult = fnFunction <*> fResult
}
Is there any way to write the Monad instance for this without exposing the IO internals to the outside world?
I've worked out a solution sitting on top of ReaderT, but I really want to get my original idea to work:
newtype TemplateM a = TemplateM { unTemplateM :: ReaderT (IORef [(Text, Text)]) IO a } deriving (Functor, Applicative, Monad)
renderCached :: Text -> TemplateM Text
renderCached k = TemplateM $ do
-- this is just dummy code. The actual cache lookup has not
-- been implemented, but the types align
v <- pure $ "rendered template for " <> k
cacheRef <- ask
atomicModifyIORef' cacheRef (\x -> ((k, v):x, ()))
pure v
runTemplateM :: [(Text, Text)]
-> TemplateM a
-> IO ([(Text, Text)], a)
runTemplateM initialCache x = do
initialCacheRef <- newIORef initialCache
(flip runReaderT) initialCacheRef $ do
res <- unTemplateM x
ref <- ask
finalCache <- readIORef ref
pure (finalCache, res)
As others have suggested, the elementary solution here is to use StateT. Since you don't need to store your IORef in a data structure or share it between threads, you can eliminate it altogether. (Of course, if that changes and you do end up wanting to share state across multiple concurrent threads, you'll have to revisit this choice.)
import Control.Monad.State.Strict
import Data.Text (Text)
import Data.Tuple (swap)
newtype TemplateM a = TemplateM {unTemplateM :: StateT [(Text, Text)] IO a}
deriving (Functor, Applicative, Monad)
renderCached :: Text -> TemplateM Text
renderCached k = TemplateM $ do
v <- pure $ "rendered template for " <> k
modify ((k, v) :)
pure v
runTemplateM :: [(Text, Text)]
-> TemplateM a
-> IO ([(Text, Text)], a)
runTemplateM initialCache x = fmap swap $ flip runStateT initialCache (unTemplateM x)
It goes without saying that a cache like this should almost certainly be stored as a structure that is not a list. One promising option is to use text-trie, a data structure Wren Romano designed specially for this purpose. You might also consider a HashMap or even a Map.
Context
I have some monadic functions for an interpreter that I'm trying to test with HSpec. They run with the following monad stack:
type AppState = StateT InterpreterState (ExceptT Events IO)
type AppReturn a = Either Events (a, PState)
runApp :: AppState a -> IO (AppReturn a)
runApp f = runExceptT (runStateT f new)
Here's an example of a simple one:
mustEvalExpr :: Expr -> S.AppState (S.Value)
mustEvalExpr e = do
val <- evalExpr e
case val of
Just val' -> return val'
Nothing -> throw $ S.CannotEval e
The problem is that HSpec has its own context (IO ()), so I have to translate between the two contexts.
Current Approach
I'm using HSpec, and I wrote a transformer function to get a runApp context from within the HSpec context.
-- imports omitted
extract :: S.AppReturn a -> a
extract st = case st of
Right (a, _) -> a
Left ev -> throw ev
run :: (S.AppReturn a -> b) -> S.AppState a -> IO b
run extractor fn = do
state <- S.runApp fn
return $ extractor state
So my Spec looks like this:
spec :: Spec
spec = do
describe "mustEvalExpr" $ do
let badExpr = D.VarExpr $ D.Id "doesntExist"
goodExpr = D.IntExpr 1
val = S.IntValue 1
it "should evaluate and return expression if its a Just" $ do
(run extract $ do
I.mustEvalExpr goodExpr
) >>= (`shouldBe` val)
it "should throw error if it gets a Nothing" $ do
(run extract $ do
I.mustEvalExpr badExpr
) `shouldThrow` anyException
Question
Is this the best I can do? I feel like run extract $ do is fine, and I think it's good to be explicit when things are complicated.
But I was wondering if there was a way I can integrate with HSpec, or if there's a best-practice for this problem that doesn't require custom code?
What would the proper way to store an OAuth2 jwk in haskell? The certs i am retrieving are from https://www.googleapis.com/oauth2/v3/certs and I would like to avoid calling out for certs each time i need to verify the signature on a token. The options seem to be MVar, TVar, IORef, or the state monad although i am not quite sure how i would implement the state monad for this.
The basic steps would be the following (running behind a scotty server):
Receive Token from IDP
Decode Jwt with JWk's
If the decode fails due to a bad signature then check the endpoint for new certs and modify the current variable containing the cert
I am using jose-jwt, wreq, and scotty right now and have something that works but i would like to implement the approach that i am asking about rather than my existing approach.
module Main where
import ClassyPrelude
import Web.Scotty as S
import Network.Wreq as W
import Control.Lens as CL
import qualified Data.Text.Lazy as TL
import qualified Network.URI.Encode as URI
import Network.Wai.Middleware.RequestLogger
import Jose.Jwe
import Jose.Jwa
import Jose.Jwk
import Jose.Jwt
import Jose.Jws
import Data.Aeson
import qualified Data.HashMap.Strict as HM
import qualified Data.Text as T
import qualified Data.List as DL
import qualified Data.ByteString.Base64 as B64
main :: IO ()
main = scotty 8080 $ do
middleware logStdoutDev
redirectCallback
oauthCallback
oauthGen
home
home :: ScottyM ()
home = do
S.get "/:word" $ do
beam <- S.param "word"
html $ mconcat ["<h1>Scotty, ", beam, " me up!</h1>"]
redirectCallback :: ScottyM ()
redirectCallback = do
S.get "/redirect" $ do
let v = uriSchemeBuilder
redirect $ TL.fromStrict v
oauthCallback :: ScottyM ()
oauthCallback = do
matchAny "/goauth2callback" $ do
val <- body
pars <- S.params
c <- S.param "code" `rescue` (\_ -> return "haskell")
let c1 = c <> (""::Text)
r <- liftIO $ W.post "https://oauth2.googleapis.com/token"
[ "code" := (encodeUtf8 (c))
, "client_id" := (encodeUtf8 consumerAccess)
, "client_secret" := (encodeUtf8 consumerSecret)
, "redirect_uri" := (encodeUtf8 redirectURI)
, "grant_type" := ("authorization_code"::ByteString)
, "access_type" := ("offline"::ByteString)
]
let newUser = (r ^? responseBody)
case newUser of
Just b -> do
let jwt = decodeStrict (toStrict b) :: Maybe Value
case jwt of
Just (Object v) -> do
let s = HM.lookup "id_token" v
case s of
Just (String k) -> do
isValid <- liftIO $ validateToken (encodeUtf8 k)
liftIO $ print isValid
redirect "/hello_world"
_ -> redirect "/hello_world"
_ -> redirect "/hello_world"
Nothing -> redirect "/hello_world"
oauthGen :: ScottyM ()
oauthGen = do
matchAny "/callback_gen" $ do
val <- body
redirect "/hello_world"
consumerAccess :: Text
consumerAccess = "google public key"
consumerSecret :: Text
consumerSecret = "google secret key"
oAuthScopes :: Text
oAuthScopes = "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email"
redirectURI :: Text
redirectURI = "http://localhost:8080/goauth2callback"
authURI :: Text
authURI = "https://accounts.google.com/o/oauth2/auth"
tokenURI :: Text
tokenURI = "https://oauth2.googleapis.com/token"
projectId :: Text
projectId = "project name"
responseType :: Text
responseType = "code"
oAuthUriBuilder :: [(Text, Text)]
oAuthUriBuilder =
[ ("client_id", consumerAccess)
, ("redirect_uri", redirectURI)
, ("scope", oAuthScopes)
, ("response_type", responseType)
]
uriSchemeBuilder :: Text
uriSchemeBuilder = authURI <> "?" <> (foldr (\x y -> (fst x ++ "=" ++ (URI.encodeText $ snd x)) ++ "&" ++ y) "" oAuthUriBuilder)
validateToken :: ByteString -> IO (Either JwtError JwtContent)
validateToken b = do
keySet <- retrievePublicKeys
case keySet of
Left e -> return $ Left $ KeyError "No keyset supplied"
Right k -> do
let header = JwsEncoding RS256
Jose.Jwt.decode k (Just $ header) b
retrievePublicKeys :: IO (Either String [Jwk])
retrievePublicKeys = do
r <- liftIO $ W.get "https://www.googleapis.com/oauth2/v3/certs"
case (r ^? responseBody) of
Nothing -> return $ Left "No body in response from google oauth api"
Just a -> do
let v = eitherDecode a :: Either String Value
case v of
Left e -> return $ Left e
Right (Object a) -> do
let keySet = HM.lookup "keys" a
case keySet of
Just k -> do
let kS = eitherDecode (Data.Aeson.encode k) :: Either String [Jwk]
return $ kS
_ -> return $ Left "No Key set provided"
_ -> return $ Left $ "Incorrect response type from https://www.googleapis.com/oauth2/v3/certs"
The specific part i am interested in replacing is:
retrievePublicKeys :: IO (Either String [Jwk])
retrievePublicKeys = do
r <- liftIO $ W.get "https://www.googleapis.com/oauth2/v3/certs"
case (r ^? responseBody) of
Nothing -> return $ Left "No body in response from google oauth api"
Just a -> do
let v = eitherDecode a :: Either String Value
case v of
Left e -> return $ Left e
Right (Object a) -> do
let keySet = HM.lookup "keys" a
case keySet of
Just k -> do
let kS = eitherDecode (Data.Aeson.encode k) :: Either String [Jwk]
return $ kS
_ -> return $ Left "No Key set provided"
_ -> return $ Left $ "Incorrect response type from https://www.googleapis.com/oauth2/v3/certs"
I've though about storing the Jwk's in redis but i would think that there is a better approach available.
The expected result is to be able to safely modify the cert that i am obtaining from google and using it on subsequent decodings without the need to constantly hit the endpoint.
(Note: Yes i know that it is bad practice to roll your own security but this is just out of interest)
If you go by something like three layers (ReaderT design pattern), then caching an IORef or TVar in the environment part in a ReaderT YourEnv IO would be the way to go. (atomicModifyIORef' should be sufficient.)
The Holmusk link will recommend the jwt package, but having just added, in another language at work, in-memory caching of Google's OAuth2 certificates, picking a JWT library in Haskell also appears very much like a feature trade-off:
For example, jwt explicitly states that it doesn't check the exp expiration timestamp, but as far as I can see, jose-jwt doesn't even address the exp expiration timestamp that it decodes. google-oauth2-jwt does, and embeds the endpoint (for good and for bad, harder to mock), but doesn't provide a lot of ergonomics beyond that. (Edit: It appears that jose does handle expiration, and that it's also the most popular of those I mentioned on Hackage.)
parseSource :: String -> Either ParserError Mod.Module
parseSource src = do
(imports, rest) <- parseImports (Lex.lexSource src)
bindings <- mapM parseBinding rest
buildModule imports bindings
I need to make the above return an IO (Either ParserError Mod.Module) as the buildModule statement at the end will need to perform some IO functions (reading files). The problem i have is that when i make it an IO function, i can no longer do the bind(wrong term?) <- operations.
What is the simplest way to make this work?
Take a look at defining your problem in terms of ErrorT ParseError IO.
I couldn't find a combinator to lift a pure Either computation into the ErrorT monad, so I wrote one called liftError. I fleshed out your example with dummy types and implementations. The main runs the parser twice, once with input that throws a ParserError, and once which succeeds with an IO side-effect. In order for ErrorT ParserError IO to be a Monad, ParserError must be an instance of Error (so that it is possible to implement fail).
import Control.Monad.Error
type ParserMonad = ErrorT ParserError IO
data ParserError = ParserError1 | ParserError2 | ParserError3
deriving(Show)
data Module = Module
deriving(Show)
data Import = Import
deriving(Show)
data Binding = Binding
deriving(Show)
instance Error ParserError where
noMsg = undefined
-- lift a pure Either into the ErrorT monad
liftError :: Monad m => Either e a -> ErrorT e m a
liftError = ErrorT . return
parseSource :: String -> ParserMonad Module
parseSource src = do
(imports, rest) <- liftError $ parseImports (lexSource src)
bindings <- liftError $ mapM parseBinding rest
buildModule imports bindings
lexSource :: String -> [String]
lexSource = return
parseImports :: [String] -> Either ParserError ([Import], [String])
parseImports toks = do{ when (null toks) $ throwError ParserError1
; return ([Import], toks)
}
parseBinding :: String -> Either ParserError Binding
parseBinding b = do{ when (b == "hello") $ throwError ParserError2
; return Binding
}
buildModule :: [Import] -> [Binding] -> ParserMonad Module
buildModule i b = do{ liftIO $ print "hello"
; when (null b) $ throwError ParserError3
; return Module
}
main = mapM (runErrorT . parseSource) ["hello", "world"]