When receiving activity to inbox, verify the body digest

This patch does a small simple change, however at the cost of the request body
not being available for display in the latest activity list, unless processing
succeeds. I'll fix this situation in a separate patch.
This commit is contained in:
fr33domlover 2019-04-25 18:05:02 +00:00
parent 57374ec816
commit d24710c46a
2 changed files with 41 additions and 19 deletions

View file

@ -24,6 +24,7 @@ import Control.Monad.STM (atomically)
import Control.Monad.Trans.Except import Control.Monad.Trans.Except
import Control.Monad.Trans.Maybe import Control.Monad.Trans.Maybe
import Crypto.Error (CryptoFailable (..)) import Crypto.Error (CryptoFailable (..))
import Crypto.Hash.Algorithms
import Data.Char import Data.Char
import Data.Either (isRight) import Data.Either (isRight)
import Data.HashMap.Strict (HashMap) import Data.HashMap.Strict (HashMap)
@ -35,9 +36,10 @@ import Data.Time.Units (Second, Minute, Day)
import Database.Persist.Postgresql import Database.Persist.Postgresql
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 import Network.HTTP.Client (Manager, HasHttpManager (..))
import Network.HTTP.Types.Header (hHost) import Network.HTTP.Types.Header (hHost)
import Network.URI (URI, uriAuthority, uriFragment, uriRegName, parseURI) import Network.URI (URI, uriAuthority, uriFragment, uriRegName, parseURI)
import Network.Wai
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)
@ -57,7 +59,8 @@ import qualified Yesod.Core.Unsafe as Unsafe
import qualified Data.Text as T import qualified Data.Text as T
--import qualified Data.Text.Encoding as TE --import qualified Data.Text.Encoding as TE
import Network.HTTP.Signature hiding (Algorithm (..)) import Network.HTTP.Digest
import Network.HTTP.Signature hiding (Algorithm (..), requestHeaders)
import Yesod.Auth.Unverified import Yesod.Auth.Unverified
import Yesod.Auth.Unverified.Creds import Yesod.Auth.Unverified.Creds
import Yesod.HttpSignature (YesodHttpSig (..)) import Yesod.HttpSignature (YesodHttpSig (..))
@ -647,8 +650,8 @@ data ActorDetail = ActorDetail
} }
instance YesodHttpSig App where instance YesodHttpSig App where
data HttpSigVerResult App = HttpSigVerResult (Either String ActorDetail) data HttpSigVerResult App = HttpSigVerResult (Either String (ActorDetail, BL.ByteString))
httpSigVerRequiredHeaders = const [hRequestTarget, hHost] httpSigVerRequiredHeaders = const [hRequestTarget, hHost, hDigest]
httpSigVerWantedHeaders = const [hActivityPubActor] httpSigVerWantedHeaders = const [hActivityPubActor]
httpSigVerSeconds = httpSigVerSeconds =
fromIntegral . toSeconds . appHttpSigTimeLimit . appSettings fromIntegral . toSeconds . appHttpSigTimeLimit . appSettings
@ -658,6 +661,7 @@ instance YesodHttpSig App where
httpVerifySig (Verification malgo (KeyId keyid) input (Signature signature)) = fmap HttpSigVerResult $ runExceptT $ do httpVerifySig (Verification malgo (KeyId keyid) input (Signature signature)) = fmap HttpSigVerResult $ runExceptT $ do
(host, luKey) <- f2l <$> parseKeyId keyid (host, luKey) <- f2l <$> parseKeyId keyid
checkHost host checkHost host
body <- verifyBodyDigest
mluActorHeader <- getActorHeader host mluActorHeader <- getActorHeader host
manager <- getsYesod appHttpManager manager <- getsYesod appHttpManager
(inboxOrVkid, vkd) <- do (inboxOrVkid, vkd) <- do
@ -736,11 +740,14 @@ instance YesodHttpSig App where
return (iid, rsid) return (iid, rsid)
else errSig2 else errSig2
return ActorDetail return
{ actorDetailId = l2f host $ vkdActorId vkd ( ActorDetail
, actorDetailInstance = iid { actorDetailId = l2f host $ vkdActorId vkd
, actorDetailSharer = rsid , actorDetailInstance = iid
} , actorDetailSharer = rsid
}
, body
)
where where
parseKeyId k = parseKeyId k =
case parseFedURI =<< (first displayException . decodeUtf8') k of case parseFedURI =<< (first displayException . decodeUtf8') k of
@ -756,6 +763,16 @@ instance YesodHttpSig App where
throwE "Received HTTP signed request from the instance's host" throwE "Received HTTP signed request from the instance's host"
where where
isAsciiLetter c = isAsciiLower c || isAsciiUpper c isAsciiLetter c = isAsciiLower c || isAsciiUpper c
verifyBodyDigest = do
req <- waiRequest
let headers = requestHeaders req
digest <- case parseHttpBodyDigest SHA256 "SHA-256" headers of
Left s -> throwE $ "Parsing digest header failed: " ++ s
Right d -> return d
(digest', body) <- liftIO $ hashHttpBody SHA256 (requestBody req)
unless (digest == digest') $
throwE "Body digest verification failed"
return body
getActorHeader host = do getActorHeader host = do
bs <- lookupHeaders hActivityPubActor bs <- lookupHeaders hActivityPubActor
case bs of case bs of

View file

@ -138,21 +138,24 @@ postSharerInboxR shrRecip = do
federation <- getsYesod $ appFederation . appSettings federation <- getsYesod $ appFederation . appSettings
unless federation badMethod unless federation badMethod
contentTypes <- lookupHeaders "Content-Type" contentTypes <- lookupHeaders "Content-Type"
body <- requireJsonBody
now <- liftIO getCurrentTime now <- liftIO getCurrentTime
result <- go now contentTypes body result <- go now contentTypes
recordActivity now result contentTypes body recordActivity now result contentTypes
case result of case result of
Left _ -> sendResponseStatus badRequest400 () Left _ -> sendResponseStatus badRequest400 ()
Right _ -> return () Right _ -> return ()
where where
go now ctypes (WithValue raw (Doc hActivity activity)) = runExceptT $ do go now ctypes = runExceptT $ do
verifyContentType verifyContentType
HttpSigVerResult result <- HttpSigVerResult result <-
ExceptT $ ExceptT $
first (T.pack . displayException) <$> first (T.pack . displayException) <$>
verifyRequestSignature now verifyRequestSignature now
ActorDetail uSender iid _raid <- ExceptT $ pure $ first T.pack result (ActorDetail uSender iid _raid, body) <- ExceptT $ pure $ first T.pack result
WithValue raw (Doc hActivity activity) <-
case eitherDecode' body of
Left s -> throwE $ "Parsing activity failed: " <> T.pack s
Right wv -> return wv
let (hSender, luSender) = f2l uSender let (hSender, luSender) = f2l uSender
unless (hSender == hActivity) $ unless (hSender == hActivity) $
throwE $ T.concat throwE $ T.concat
@ -165,7 +168,7 @@ postSharerInboxR shrRecip = do
, renderFedURI $ l2f hActivity $ activityActor activity , renderFedURI $ l2f hActivity $ activityActor activity
, "> != Signature key's actor <", renderFedURI uSender, ">" , "> != Signature key's actor <", renderFedURI uSender, ">"
] ]
handleSharerInbox now shrRecip iid raw activity (raw,) <$> handleSharerInbox now shrRecip iid raw activity
where where
verifyContentType = verifyContentType =
case ctypes of case ctypes of
@ -183,12 +186,14 @@ postSharerInboxR shrRecip = do
typeAS2 = typeAS2 =
"application/ld+json; \ "application/ld+json; \
\profile=\"https://www.w3.org/ns/activitystreams\"" \profile=\"https://www.w3.org/ns/activitystreams\""
recordActivity now result contentTypes body = do recordActivity now result contentTypes = do
acts <- getsYesod appActivities acts <- getsYesod appActivities
liftIO $ atomically $ modifyTVar' acts $ \ vec -> liftIO $ atomically $ modifyTVar' acts $ \ vec ->
let msg = either id id result let (msg, body) =
formattedBody = encodePretty $ wvRaw body case result of
item = ActivityReport now msg contentTypes formattedBody Left t -> (t, "{?}")
Right (o, t) -> (t, encodePretty o)
item = ActivityReport now msg contentTypes body
vec' = item `V.cons` vec vec' = item `V.cons` vec
in if V.length vec' > 10 in if V.length vec' > 10
then V.init vec' then V.init vec'