Back to using the (updated) YesodHttpSig class
This commit is contained in:
parent
393cce0ede
commit
2a4dc345f4
2 changed files with 63 additions and 136 deletions
|
@ -18,6 +18,7 @@ module Vervis.Foundation where
|
||||||
import Prelude (init, last)
|
import Prelude (init, last)
|
||||||
|
|
||||||
import Control.Monad.Logger.CallStack (logWarn)
|
import Control.Monad.Logger.CallStack (logWarn)
|
||||||
|
import Control.Monad.Trans.Except (ExceptT (ExceptT), runExceptT)
|
||||||
import Control.Monad.Trans.Maybe
|
import Control.Monad.Trans.Maybe
|
||||||
import Crypto.Error (CryptoFailable (..))
|
import Crypto.Error (CryptoFailable (..))
|
||||||
import Crypto.PubKey.Ed25519 (publicKey, signature, verify)
|
import Crypto.PubKey.Ed25519 (publicKey, signature, verify)
|
||||||
|
@ -27,8 +28,8 @@ import Data.Time.Units (Second, Minute, Day)
|
||||||
import Database.Persist.Sql (ConnectionPool, runSqlPool)
|
import Database.Persist.Sql (ConnectionPool, runSqlPool)
|
||||||
import Graphics.SVGFonts.ReadFont (PreparedFont)
|
import Graphics.SVGFonts.ReadFont (PreparedFont)
|
||||||
import Network.HTTP.Client (Manager, HttpException, requestFromURI)
|
import Network.HTTP.Client (Manager, HttpException, requestFromURI)
|
||||||
import Network.HTTP.Simple (httpJSONEither, getResponseBody)
|
import Network.HTTP.Simple (httpJSONEither, getResponseBody, setRequestManager, addRequestHeader)
|
||||||
import Network.URI (uriFragment, parseURI)
|
import Network.URI (URI (uriFragment), parseURI)
|
||||||
import Text.Shakespeare.Text (textFile)
|
import Text.Shakespeare.Text (textFile)
|
||||||
import Text.Hamlet (hamletFile)
|
import Text.Hamlet (hamletFile)
|
||||||
--import Text.Jasmine (minifym)
|
--import Text.Jasmine (minifym)
|
||||||
|
@ -560,72 +561,69 @@ unsafeHandler = Unsafe.fakeHandlerGetLogger appLogger
|
||||||
-- https://github.com/yesodweb/yesod/wiki/Serve-static-files-from-a-separate-domain
|
-- https://github.com/yesodweb/yesod/wiki/Serve-static-files-from-a-separate-domain
|
||||||
-- https://github.com/yesodweb/yesod/wiki/i18n-messages-in-the-scaffolding
|
-- https://github.com/yesodweb/yesod/wiki/i18n-messages-in-the-scaffolding
|
||||||
|
|
||||||
{-
|
|
||||||
instance YesodHttpSig App where
|
instance YesodHttpSig App where
|
||||||
data HttpSigVerResult App = HttpSigVerResult Bool
|
data HttpSigVerResult App = HttpSigVerResult (Either String URI)
|
||||||
httpSigVerHeaders = const [HeaderTarget, HeaderName "Host"]
|
httpSigVerHeaders = const [HeaderTarget, HeaderName "Host"]
|
||||||
httpSigVerSeconds =
|
httpSigVerSeconds =
|
||||||
fromIntegral . toSeconds . appHttpSigTimeLimit . appSettings
|
fromIntegral . toSeconds . appHttpSigTimeLimit . appSettings
|
||||||
where
|
where
|
||||||
toSeconds :: TimeInterval -> Second
|
toSeconds :: TimeInterval -> Second
|
||||||
toSeconds = toTimeUnit
|
toSeconds = toTimeUnit
|
||||||
httpVerifySig malgo (KeyId keyid) input (Signature sig) =
|
httpVerifySig (Verification malgo (KeyId keyid) input (Signature sig)) = fmap HttpSigVerResult $ runExceptT $ do
|
||||||
if algoSupported malgo
|
ExceptT . pure $ case malgo of
|
||||||
then case parseURI $ BC.unpack keyid of
|
Nothing -> Right ()
|
||||||
Just u -> do
|
Just algo ->
|
||||||
eres <- try $ httpJSONEither =<< requestFromURI u
|
case algo of
|
||||||
case eres of
|
S.AlgorithmEd25519 -> Right ()
|
||||||
Left e -> do
|
S.AlgorithmOther _ -> Left "Unsupported algo in Sig header"
|
||||||
logWarn $ "httpVerifySig got HTTP exception: " <> T.pack (displayException (e :: HttpException))
|
u <- ExceptT . pure $ case parseURI $ BC.unpack keyid of
|
||||||
-- return HttpSigVerKeyNotFound
|
Nothing -> Left "keyId in Sig header isn't a valid absolute URI"
|
||||||
return $ HttpSigVerResult False
|
Just uri -> Right uri
|
||||||
Right r ->
|
manager <- getsYesod appHttpManager
|
||||||
case getResponseBody r of
|
response <-
|
||||||
Left e -> do
|
ExceptT $ first (displayException :: HttpException -> String) <$>
|
||||||
logWarn $ "httpVerifySig got JSON exception: " <> T.pack (displayException e)
|
(try $
|
||||||
-- return HttpSigVerKeyNotFound
|
httpJSONEither .
|
||||||
return $ HttpSigVerResult False
|
addRequestHeader "Accept" "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"" .
|
||||||
Right actor -> do
|
setRequestManager manager
|
||||||
let uActor = u { uriFragment = "" }
|
=<< requestFromURI u
|
||||||
if uActor == actorId actor
|
)
|
||||||
then
|
ExceptT . pure $ do
|
||||||
let pkey = actorPublicKey actor
|
actor <- first displayException $ getResponseBody response
|
||||||
in if publicKeyId pkey == u && publicKeyOwner pkey == actorId actor
|
let uActor = u { uriFragment = "" }
|
||||||
then case publicKeyAlgo pkey of
|
if uActor == actorId actor
|
||||||
Just AlgorithmEd25519 ->
|
then Right ()
|
||||||
case publicKey $ pemContent $ publicKeyPem pkey of
|
else Left "Actor ID doesn't match the keyid URI we fetched"
|
||||||
CryptoPassed k ->
|
let pkey = actorPublicKey actor
|
||||||
case signature sig of
|
if publicKeyId pkey == u
|
||||||
CryptoPassed s ->
|
then Right ()
|
||||||
return $ if verify k input s
|
else Left "Actor's publicKey's ID doesn't match the keyid URI"
|
||||||
then -- HttpSigVerValid
|
if publicKeyOwner pkey == actorId actor
|
||||||
HttpSigVerResult True
|
then Right ()
|
||||||
else -- HttpSigVerInvalid
|
else Left "Actor's publicKey's owner doesn't match the actor's ID"
|
||||||
HttpSigVerResult False
|
case publicKeyAlgo pkey of
|
||||||
CryptoFailed e -> -- TODO handle
|
Nothing ->
|
||||||
return $ HttpSigVerResult False
|
Left $
|
||||||
CryptoFailed e -> -- TODO handle
|
case malgo of
|
||||||
return $ HttpSigVerResult False
|
Nothing -> "Algo not given in Sig nor actor"
|
||||||
_ -> case malgo of
|
Just _ -> "Algo mismatch, Ed25519 in Sig but none in actor"
|
||||||
Nothing -> -- return HttpSigVerAlgoNotSupported
|
Just algo ->
|
||||||
return $ HttpSigVerResult False
|
case algo of
|
||||||
Just _ -> -- return HttpSigVerAlgoMismatch
|
AlgorithmEd25519 -> Right ()
|
||||||
return $ HttpSigVerResult False
|
AlgorithmOther _ ->
|
||||||
else -- TODO handle the mismatch
|
Left $
|
||||||
return $ HttpSigVerResult False
|
case malgo of
|
||||||
else -- TODO actor id doesn't match URL we accessed!
|
Nothing -> "No algo in Sig, unsupported algo in actor"
|
||||||
return $ HttpSigVerResult False
|
Just _ -> "Algo mismatch, Ed25519 in Sig but unsupported algo in actor"
|
||||||
Nothing -> -- return HttpSigVerKeyNotFound
|
key <- case publicKey $ pemContent $ publicKeyPem pkey of
|
||||||
return $ HttpSigVerResult False
|
CryptoPassed k -> Right k
|
||||||
else -- return HttpSigVerAlgoNotSupported
|
CryptoFailed e -> Left "Parsing Ed25519 public key failed"
|
||||||
return $ HttpSigVerResult False
|
signature <- case signature sig of
|
||||||
where
|
CryptoPassed s -> Right s
|
||||||
algoSupported Nothing = True
|
CryptoFailed e -> Left "Parsing Ed25519 signature failed"
|
||||||
algoSupported (Just a) =
|
if verify key input signature
|
||||||
case a of
|
then Right uActor
|
||||||
S.AlgorithmEd25519 -> True
|
else Left "Ed25519 sig verification says not valid"
|
||||||
S.AlgorithmOther _ -> False
|
|
||||||
-}
|
|
||||||
|
|
||||||
instance YesodBreadcrumbs App where
|
instance YesodBreadcrumbs App where
|
||||||
breadcrumb route = return $ case route of
|
breadcrumb route = return $ case route of
|
||||||
|
|
|
@ -56,11 +56,12 @@ import qualified Data.Vector as V (length, cons, init)
|
||||||
import qualified Network.Wai as W (requestMethod, rawPathInfo, requestHeaders)
|
import qualified Network.Wai as W (requestMethod, rawPathInfo, requestHeaders)
|
||||||
|
|
||||||
import Network.HTTP.Signature hiding (Algorithm (..))
|
import Network.HTTP.Signature hiding (Algorithm (..))
|
||||||
|
import Yesod.HttpSignature (verifyRequestSignature)
|
||||||
|
|
||||||
import qualified Network.HTTP.Signature as S (Algorithm (..))
|
import qualified Network.HTTP.Signature as S (Algorithm (..))
|
||||||
|
|
||||||
import Vervis.ActivityPub
|
import Vervis.ActivityPub
|
||||||
import Vervis.Foundation (App (..), Handler)
|
import Vervis.Foundation (App (..), HttpSigVerResult (..), Handler)
|
||||||
import Vervis.Settings (AppSettings (appHttpSigTimeLimit))
|
import Vervis.Settings (AppSettings (appHttpSigTimeLimit))
|
||||||
|
|
||||||
getInboxR :: Handler Html
|
getInboxR :: Handler Html
|
||||||
|
@ -127,79 +128,6 @@ postInboxR = do
|
||||||
Left _ -> notAuthenticated
|
Left _ -> notAuthenticated
|
||||||
where
|
where
|
||||||
liftE = ExceptT . pure
|
liftE = ExceptT . pure
|
||||||
verifyActivity :: UTCTime -> ExceptT String Handler URI
|
|
||||||
verifyActivity now = do
|
|
||||||
site <- getYesod
|
|
||||||
wr <- waiRequest
|
|
||||||
let request = Request
|
|
||||||
{ requestMethod = CI.mk $ W.requestMethod wr
|
|
||||||
, requestPath = W.rawPathInfo wr
|
|
||||||
, requestHeaders = W.requestHeaders wr
|
|
||||||
}
|
|
||||||
toSeconds :: TimeInterval -> Second
|
|
||||||
toSeconds = toTimeUnit
|
|
||||||
(malgo, KeyId keyid, input, Signature sig) <-
|
|
||||||
liftE $
|
|
||||||
first show $
|
|
||||||
prepareToVerify
|
|
||||||
[HeaderTarget, HeaderName "Host"]
|
|
||||||
(fromIntegral . toSeconds . appHttpSigTimeLimit . appSettings $ site)
|
|
||||||
now
|
|
||||||
request
|
|
||||||
liftE $ case malgo of
|
|
||||||
Nothing -> Right ()
|
|
||||||
Just algo ->
|
|
||||||
case algo of
|
|
||||||
S.AlgorithmEd25519 -> Right ()
|
|
||||||
S.AlgorithmOther _ -> Left "Unsupported algo in Sig header"
|
|
||||||
u <- liftE $ case parseURI $ BC.unpack keyid of
|
|
||||||
Nothing -> Left "keyId in Sig header isn't a valid absolute URI"
|
|
||||||
Just uri -> Right uri
|
|
||||||
manager <- getsYesod appHttpManager
|
|
||||||
response <-
|
|
||||||
ExceptT $ first (displayException :: HttpException -> String) <$>
|
|
||||||
(try $
|
|
||||||
httpJSONEither .
|
|
||||||
addRequestHeader "Accept" "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"" .
|
|
||||||
setRequestManager manager
|
|
||||||
=<< requestFromURI u
|
|
||||||
)
|
|
||||||
liftE $ do
|
|
||||||
actor <- first displayException $ getResponseBody response
|
|
||||||
let uActor = u { uriFragment = "" }
|
|
||||||
if uActor == actorId actor
|
|
||||||
then Right ()
|
|
||||||
else Left "Actor ID doesn't match the keyid URI we fetched"
|
|
||||||
let pkey = actorPublicKey actor
|
|
||||||
if publicKeyId pkey == u
|
|
||||||
then Right ()
|
|
||||||
else Left "Actor's publicKey's ID doesn't match the keyid URI"
|
|
||||||
if publicKeyOwner pkey == actorId actor
|
|
||||||
then Right ()
|
|
||||||
else Left "Actor's publicKey's owner doesn't match the actor's ID"
|
|
||||||
case publicKeyAlgo pkey of
|
|
||||||
Nothing ->
|
|
||||||
Left $
|
|
||||||
case malgo of
|
|
||||||
Nothing -> "Algo not given in Sig nor actor"
|
|
||||||
Just _ -> "Algo mismatch, Ed25519 in Sig but none in actor"
|
|
||||||
Just algo ->
|
|
||||||
case algo of
|
|
||||||
AlgorithmEd25519 -> Right ()
|
|
||||||
AlgorithmOther _ ->
|
|
||||||
Left $
|
|
||||||
case malgo of
|
|
||||||
Nothing -> "No algo in Sig, unsupported algo in actor"
|
|
||||||
Just _ -> "Algo mismatch, Ed25519 in Sig but unsupported algo in actor"
|
|
||||||
key <- case publicKey $ pemContent $ publicKeyPem pkey of
|
|
||||||
CryptoPassed k -> Right k
|
|
||||||
CryptoFailed e -> Left "Parsing Ed25519 public key failed"
|
|
||||||
signature <- case signature sig of
|
|
||||||
CryptoPassed s -> Right s
|
|
||||||
CryptoFailed e -> Left "Parsing Ed25519 signature failed"
|
|
||||||
if verify key input signature
|
|
||||||
then Right uActor
|
|
||||||
else Left "Ed25519 sig verification says not valid"
|
|
||||||
getActivity :: UTCTime -> ExceptT String Handler (ContentType, HashMap Text Value)
|
getActivity :: UTCTime -> ExceptT String Handler (ContentType, HashMap Text Value)
|
||||||
getActivity now = do
|
getActivity now = do
|
||||||
contentType <- do
|
contentType <- do
|
||||||
|
@ -211,7 +139,8 @@ postInboxR = do
|
||||||
"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"" -> Right x
|
"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"" -> Right x
|
||||||
_ -> Left "Unknown Content-Type"
|
_ -> Left "Unknown Content-Type"
|
||||||
_ -> Left "More than one Content-Type given"
|
_ -> Left "More than one Content-Type given"
|
||||||
uActor <- verifyActivity now
|
HttpSigVerResult result <- ExceptT . fmap (first displayException) $ verifyRequestSignature now
|
||||||
|
uActor <- liftE result
|
||||||
o <- requireJsonBody
|
o <- requireJsonBody
|
||||||
activityActor <-
|
activityActor <-
|
||||||
liftE $
|
liftE $
|
||||||
|
|
Loading…
Reference in a new issue