monad throw instead of error
This commit is contained in:
		
							parent
							
								
									0db710272a
								
							
						
					
					
						commit
						2a3b0f9434
					
				
					 4 changed files with 69 additions and 56 deletions
				
			
		| 
						 | 
					@ -47,13 +47,10 @@ routes db detailsFile =
 | 
				
			||||||
      notes <- map noteToCreate <$> liftIO db.getNotes
 | 
					      notes <- map noteToCreate <$> liftIO db.getNotes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      Fedi.handleCreateNote details notes
 | 
					      Fedi.handleCreateNote details notes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  , -- Match inbox
 | 
					  , -- Match inbox
 | 
				
			||||||
    Twain.post (Fedi.matchInbox $ unsafePerformIO $ fetchUserDetails detailsFile) do
 | 
					    Twain.post (Fedi.matchInbox $ unsafePerformIO $ fetchUserDetails detailsFile) do
 | 
				
			||||||
      request <- Twain.request
 | 
					      Fedi.handleInbox (handleInbox db detailsFile)
 | 
				
			||||||
      if Fedi.checkContentTypeAccept request
 | 
					 | 
				
			||||||
        then do
 | 
					 | 
				
			||||||
          Fedi.handleInbox (handleInbox db detailsFile)
 | 
					 | 
				
			||||||
        else Twain.next
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  , -- Match Create object
 | 
					  , -- Match Create object
 | 
				
			||||||
    Twain.get (Fedi.matchCreateNote $ unsafePerformIO $ fetchUserDetails detailsFile) do
 | 
					    Twain.get (Fedi.matchCreateNote $ unsafePerformIO $ fetchUserDetails detailsFile) do
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,36 +12,34 @@ import Fedi.UserDetails
 | 
				
			||||||
import Data.ByteString.Base64 qualified as Base64
 | 
					import Data.ByteString.Base64 qualified as Base64
 | 
				
			||||||
import Data.Base64.Types qualified as Base64
 | 
					import Data.Base64.Types qualified as Base64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
verifyPub :: ByteString -> ByteString -> ByteString -> Bool
 | 
					verifyPub :: MonadThrow m => ByteString -> ByteString -> ByteString -> m Bool
 | 
				
			||||||
verifyPub pubkeypem sig message =
 | 
					verifyPub pubkeypem sig message = do
 | 
				
			||||||
  let
 | 
					  pubkey <-
 | 
				
			||||||
    pubkey = case Crypto.readPubKeyFileFromMemory pubkeypem of
 | 
					    case Crypto.readPubKeyFileFromMemory pubkeypem of
 | 
				
			||||||
      [Crypto.PubKeyRSA pubkey'] -> pubkey'
 | 
					      [Crypto.PubKeyRSA pubkey'] -> pure pubkey'
 | 
				
			||||||
      _ -> error "failed to read pubkey pem"
 | 
					      _ -> throw "failed to read pubkey pem"
 | 
				
			||||||
  in
 | 
					  pure $ Crypto.verify (Crypto.defaultPSSParams Crypto.SHA256) pubkey message sig
 | 
				
			||||||
    Crypto.verify (Crypto.defaultPSSParams Crypto.SHA256) pubkey message sig
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
verifyDigest :: ByteString -> ByteString -> ByteString -> Bool
 | 
					verifyDigest :: MonadThrow m => ByteString -> ByteString -> ByteString -> m Bool
 | 
				
			||||||
verifyDigest pubkeypem sig digest' =
 | 
					verifyDigest pubkeypem sig digest' = do
 | 
				
			||||||
 | 
					  pubkey <-
 | 
				
			||||||
 | 
					    case Crypto.readPubKeyFileFromMemory pubkeypem of
 | 
				
			||||||
 | 
					      [Crypto.PubKeyRSA pubkey'] -> pure pubkey'
 | 
				
			||||||
 | 
					      _ -> throw "failed to read pubkey pem"
 | 
				
			||||||
  let
 | 
					  let
 | 
				
			||||||
    pubkey = case Crypto.readPubKeyFileFromMemory pubkeypem of
 | 
					 | 
				
			||||||
      [Crypto.PubKeyRSA pubkey'] -> pubkey'
 | 
					 | 
				
			||||||
      _ -> error "failed to read pubkey pem"
 | 
					 | 
				
			||||||
    digest = Crypto.hash digest'
 | 
					    digest = Crypto.hash digest'
 | 
				
			||||||
  in
 | 
					  pure $ Crypto.verifyDigest (Crypto.defaultPSSParams Crypto.SHA256) pubkey digest sig
 | 
				
			||||||
    Crypto.verifyDigest (Crypto.defaultPSSParams Crypto.SHA256) pubkey digest sig
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
sign :: UserDetails -> ByteString -> IO Signed
 | 
					sign :: UserDetails -> ByteString -> IO Signed
 | 
				
			||||||
sign details message = do
 | 
					sign details message = do
 | 
				
			||||||
  -- get private key
 | 
					  -- get private key
 | 
				
			||||||
  privkeypem <- Crypto.readKeyFile details.privatePem
 | 
					  privkeypem <- Crypto.readKeyFile details.privatePem
 | 
				
			||||||
  let
 | 
					  privateKey <- case privkeypem of
 | 
				
			||||||
    privateKey = case privkeypem of
 | 
					    [Crypto.Unprotected (Crypto.PrivKeyRSA privkey)] -> pure privkey
 | 
				
			||||||
      [Crypto.Unprotected (Crypto.PrivKeyRSA privkey)] -> privkey
 | 
					    _ -> throw $ "error reading local private key from '" <> details.privatePem <> "'."
 | 
				
			||||||
      _ -> error $ "error reading local private key from '" <> details.privatePem <> "'."
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  -- sign message
 | 
					  -- sign message
 | 
				
			||||||
  signedMessage <- either (error . show) id <$>
 | 
					  signedMessage <- either (throw . show) pure =<<
 | 
				
			||||||
    Crypto.sign Nothing (Crypto.defaultPSSParams Crypto.SHA256) privateKey message
 | 
					    Crypto.sign Nothing (Crypto.defaultPSSParams Crypto.SHA256) privateKey message
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  -- sign digest
 | 
					  -- sign digest
 | 
				
			||||||
| 
						 | 
					@ -49,7 +47,7 @@ sign details message = do
 | 
				
			||||||
    digest :: Crypto.Digest Crypto.SHA256
 | 
					    digest :: Crypto.Digest Crypto.SHA256
 | 
				
			||||||
    digest = Crypto.hash message
 | 
					    digest = Crypto.hash message
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  signedDigest <- either (error . show) id <$>
 | 
					  signedDigest <- either (throw . show) pure =<<
 | 
				
			||||||
    Crypto.signDigest Nothing (Crypto.defaultPSSParams Crypto.SHA256) privateKey digest
 | 
					    Crypto.signDigest Nothing (Crypto.defaultPSSParams Crypto.SHA256) privateKey digest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  -- return
 | 
					  -- return
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,6 @@
 | 
				
			||||||
module Fedi.Helpers
 | 
					module Fedi.Helpers
 | 
				
			||||||
  ( module Export
 | 
					  ( module Export
 | 
				
			||||||
 | 
					  , module Fedi.Helpers
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
where
 | 
					where
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,3 +16,11 @@ import GHC.Generics as Export (Generic)
 | 
				
			||||||
import Control.Monad as Export
 | 
					import Control.Monad as Export
 | 
				
			||||||
import Data.Functor as Export
 | 
					import Data.Functor as Export
 | 
				
			||||||
import Data.Function as Export
 | 
					import Data.Function as Export
 | 
				
			||||||
 | 
					import Control.Monad.Catch as Export (throwM, Exception, MonadThrow)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					data Error
 | 
				
			||||||
 | 
					  = Error String
 | 
				
			||||||
 | 
					  deriving (Show, Exception)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					throw :: MonadThrow m => String -> m a
 | 
				
			||||||
 | 
					throw = throwM . Error
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,7 @@
 | 
				
			||||||
 | 
					{-# language RecordWildCards #-}
 | 
				
			||||||
module Fedi.Routes.Inbox where
 | 
					module Fedi.Routes.Inbox where
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import Prelude hiding (error)
 | 
				
			||||||
import Fedi.Requests
 | 
					import Fedi.Requests
 | 
				
			||||||
import Fedi.Types
 | 
					import Fedi.Types
 | 
				
			||||||
import Fedi.UserDetails
 | 
					import Fedi.UserDetails
 | 
				
			||||||
| 
						 | 
					@ -10,6 +12,7 @@ import Web.Twain.Types qualified as Twain
 | 
				
			||||||
import Data.Text qualified as T
 | 
					import Data.Text qualified as T
 | 
				
			||||||
import Network.Wai qualified as Wai
 | 
					import Network.Wai qualified as Wai
 | 
				
			||||||
import Control.Monad.IO.Class (liftIO)
 | 
					import Control.Monad.IO.Class (liftIO)
 | 
				
			||||||
 | 
					import Control.Monad.Catch (catch)
 | 
				
			||||||
import Text.ParserCombinators.ReadP qualified as P
 | 
					import Text.ParserCombinators.ReadP qualified as P
 | 
				
			||||||
import Data.Text.Encoding qualified as T
 | 
					import Data.Text.Encoding qualified as T
 | 
				
			||||||
import Data.Text.Encoding.Base64 qualified as Base64
 | 
					import Data.Text.Encoding.Base64 qualified as Base64
 | 
				
			||||||
| 
						 | 
					@ -24,19 +27,25 @@ matchInbox details =
 | 
				
			||||||
 | 
					
 | 
				
			||||||
handleInbox :: (AnyActivity -> Twain.ResponderM Twain.Response) -> Twain.ResponderM b
 | 
					handleInbox :: (AnyActivity -> Twain.ResponderM Twain.Response) -> Twain.ResponderM b
 | 
				
			||||||
handleInbox handle = do
 | 
					handleInbox handle = do
 | 
				
			||||||
  activity <- checkSignatureAndParseBody
 | 
					  let
 | 
				
			||||||
  response <- handle activity
 | 
					    handler = do
 | 
				
			||||||
  Twain.send response
 | 
					      activity <- checkSignatureAndParseBody
 | 
				
			||||||
 | 
					      response <- handle activity
 | 
				
			||||||
 | 
					      Twain.send response
 | 
				
			||||||
 | 
					  handler `catch` \(Error e) -> do
 | 
				
			||||||
 | 
					    liftIO $ print e
 | 
				
			||||||
 | 
					    Twain.send $ Twain.status Twain.status500 $ Twain.text (T.pack e)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
-- | Check the signature of the sender and parse the body of the request.
 | 
					-- | Check the signature of the sender and parse the body of the request.
 | 
				
			||||||
checkSignatureAndParseBody :: Twain.ResponderM AnyActivity
 | 
					checkSignatureAndParseBody :: Twain.ResponderM AnyActivity
 | 
				
			||||||
checkSignatureAndParseBody = do
 | 
					checkSignatureAndParseBody = do
 | 
				
			||||||
  -- get info
 | 
					  -- get info
 | 
				
			||||||
  request <- Twain.request
 | 
					  request <- Twain.request
 | 
				
			||||||
 | 
					  liftIO $ print $ Twain.requestHeaders request
 | 
				
			||||||
  body <- liftIO (Wai.strictRequestBody request)
 | 
					  body <- liftIO (Wai.strictRequestBody request)
 | 
				
			||||||
  sigheader <- parseSignature <$> Twain.header "Signature"
 | 
					  sigheader <- parseSignature =<< Twain.header "Signature"
 | 
				
			||||||
  digest <-
 | 
					  digest <- Twain.header "Digest" >>=
 | 
				
			||||||
    maybe (error "missing header Digest") T.encodeUtf8 <$> Twain.header "Digest"
 | 
					    maybe (throw "missing header Digest") (pure . T.encodeUtf8)
 | 
				
			||||||
  (person :: Person) <- liftIO $ sendGet sigheader.keyId
 | 
					  (person :: Person) <- liftIO $ sendGet sigheader.keyId
 | 
				
			||||||
  let personPkid = person.otype.publicKey.pkid
 | 
					  let personPkid = person.otype.publicKey.pkid
 | 
				
			||||||
  let personPublicKey = pemToBS person.otype.publicKey.publicKeyPem
 | 
					  let personPublicKey = pemToBS person.otype.publicKey.publicKeyPem
 | 
				
			||||||
| 
						 | 
					@ -49,17 +58,20 @@ checkSignatureAndParseBody = do
 | 
				
			||||||
  parseJson body
 | 
					  parseJson body
 | 
				
			||||||
 | 
					
 | 
				
			||||||
checkSignature
 | 
					checkSignature
 | 
				
			||||||
  :: Url -> ByteString -> SignatureHeader -> ByteString -> ByteString -> IO ()
 | 
					  :: MonadThrow m
 | 
				
			||||||
 | 
					  => Url -> ByteString -> SignatureHeader -> ByteString -> ByteString -> m ()
 | 
				
			||||||
checkSignature personPkid personPublicKey sigheader digest body = do
 | 
					checkSignature personPkid personPublicKey sigheader digest body = do
 | 
				
			||||||
  -- check
 | 
					  -- check
 | 
				
			||||||
  unless (personPkid == sigheader.keyId) $
 | 
					  unless (personPkid == sigheader.keyId) $
 | 
				
			||||||
    error "public key mismatch with signature."
 | 
					    throw "public key mismatch with signature."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  unless (verifyPub personPublicKey sigheader.signature body) $
 | 
					  pub <- verifyPub personPublicKey sigheader.signature body
 | 
				
			||||||
    error "signature verification failed."
 | 
					  unless pub $
 | 
				
			||||||
 | 
					    throw "signature verification failed."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  unless (verifyDigest personPublicKey digest body) $
 | 
					  dig <- verifyDigest personPublicKey digest body
 | 
				
			||||||
    error "digest verification failed."
 | 
					  unless dig $
 | 
				
			||||||
 | 
					    throw "digest verification failed."
 | 
				
			||||||
  -- todo: check date
 | 
					  -- todo: check date
 | 
				
			||||||
 | 
					
 | 
				
			||||||
data SignatureHeader
 | 
					data SignatureHeader
 | 
				
			||||||
| 
						 | 
					@ -78,28 +90,25 @@ data Component
 | 
				
			||||||
  | Signature
 | 
					  | Signature
 | 
				
			||||||
  deriving Eq
 | 
					  deriving Eq
 | 
				
			||||||
 | 
					
 | 
				
			||||||
parseSignature :: Maybe T.Text -> SignatureHeader
 | 
					parseSignature :: MonadThrow m => Maybe T.Text -> m SignatureHeader
 | 
				
			||||||
parseSignature minput =
 | 
					parseSignature minput = do
 | 
				
			||||||
  let
 | 
					  input <- maybe (throw "no signature.") (pure . T.unpack) minput
 | 
				
			||||||
    input = maybe (error "no signature.") T.unpack minput
 | 
					  case P.readP_to_S parser input of
 | 
				
			||||||
  in case P.readP_to_S parser input of
 | 
					    [(sig, "")] -> pure sig
 | 
				
			||||||
    [(sig, "")] -> sig
 | 
					    _ -> throw "error parsing signature."
 | 
				
			||||||
    _ -> error "error parsing signature."
 | 
					 | 
				
			||||||
  where
 | 
					  where
 | 
				
			||||||
    lookup' a b =
 | 
					    lookup' a b =
 | 
				
			||||||
      fromMaybe (error "error parsing signature") $ lookup a b
 | 
					      maybe (fail "error parsing signature") pure $ lookup a b
 | 
				
			||||||
    parser = do
 | 
					    parser = do
 | 
				
			||||||
      components <- component `P.sepBy` P.char ','
 | 
					      components <- component `P.sepBy` P.char ','
 | 
				
			||||||
      pure SignatureHeader
 | 
					      keyId <- lookup' KeyId components
 | 
				
			||||||
        { keyId = lookup' KeyId components
 | 
					      headers <- T.split (==' ') . T.pack <$> lookup' Headers components
 | 
				
			||||||
        , headers = T.split (==' ') . T.pack $ lookup' Headers components
 | 
					      signature <-
 | 
				
			||||||
        , signature =
 | 
					        ( T.encodeUtf8
 | 
				
			||||||
          ( T.encodeUtf8
 | 
					        . Base64.decodeBase64Lenient
 | 
				
			||||||
          . Base64.decodeBase64Lenient
 | 
					        . T.pack
 | 
				
			||||||
          . T.pack
 | 
					        ) <$> lookup' Signature components
 | 
				
			||||||
          . lookup' Signature
 | 
					      pure SignatureHeader{..}
 | 
				
			||||||
          ) components
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    component = P.choice
 | 
					    component = P.choice
 | 
				
			||||||
      [ do
 | 
					      [ do
 | 
				
			||||||
        _ <- P.string "keyId="
 | 
					        _ <- P.string "keyId="
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue