Allow actor publicKey to be a URI, and require the URI to match the Sig keyId

This commit is contained in:
fr33domlover 2019-02-04 10:07:25 +00:00
parent c336d56036
commit 02da508ed0
3 changed files with 32 additions and 8 deletions

View file

@ -15,6 +15,8 @@
module Data.Aeson.Local module Data.Aeson.Local
( Either' (..) ( Either' (..)
, toEither
, fromEither
, frg , frg
, parseHttpsURI , parseHttpsURI
, renderURI , renderURI
@ -37,6 +39,19 @@ data Either' a b = Left' a | Right' b
instance (FromJSON a, FromJSON b) => FromJSON (Either' a b) where instance (FromJSON a, FromJSON b) => FromJSON (Either' a b) where
parseJSON v = Left' <$> parseJSON v <|> Right' <$> parseJSON v parseJSON v = Left' <$> parseJSON v <|> Right' <$> parseJSON v
instance (ToJSON a, ToJSON b) => ToJSON (Either' a b) where
toJSON = error "toJSON Either'"
toEncoding (Left' x) = toEncoding x
toEncoding (Right' y) = toEncoding y
toEither :: Either' a b -> Either a b
toEither (Left' x) = Left x
toEither (Right' y) = Right y
fromEither :: Either a b -> Either' a b
fromEither (Left x) = Left' x
fromEither (Right y) = Right' y
frg :: Text frg :: Text
frg = "https://forgefed.angeley.es/ns#" frg = "https://forgefed.angeley.es/ns#"

View file

@ -153,7 +153,7 @@ getPersonR shr = do
, actorType = ActorTypePerson , actorType = ActorTypePerson
, actorUsername = shr2text shr , actorUsername = shr2text shr
, actorInbox = route2uri InboxR , actorInbox = route2uri InboxR
, actorPublicKey = PublicKey , actorPublicKey = Right PublicKey
{ publicKeyId = me { uriFragment = "#key" } { publicKeyId = me { uriFragment = "#key" }
, publicKeyOwner = me , publicKeyOwner = me
, publicKeyPem = PEM "PUBLIC KEY" [] actorKey , publicKeyPem = PEM "PUBLIC KEY" [] actorKey

View file

@ -49,7 +49,8 @@ import Control.Monad.Trans.Writer (Writer)
import Crypto.Error (CryptoFailable (..)) import Crypto.Error (CryptoFailable (..))
import Data.Aeson import Data.Aeson
import Data.Aeson.Types (Parser) import Data.Aeson.Types (Parser)
import Data.Bifunctor (bimap) import Data.Bifunctor (bimap, first)
import Data.Bitraversable (bitraverse)
import Data.ByteString (ByteString) import Data.ByteString (ByteString)
import Data.List.NonEmpty (NonEmpty) import Data.List.NonEmpty (NonEmpty)
import Data.PEM import Data.PEM
@ -160,7 +161,7 @@ data Actor = Actor
, actorType :: ActorType , actorType :: ActorType
, actorUsername :: Text , actorUsername :: Text
, actorInbox :: URI , actorInbox :: URI
, actorPublicKey :: PublicKey , actorPublicKey :: Either URI PublicKey
} }
instance FromJSON Actor where instance FromJSON Actor where
@ -170,7 +171,7 @@ instance FromJSON Actor where
<*> o .: "type" <*> o .: "type"
<*> o .: "preferredUsername" <*> o .: "preferredUsername"
<*> (parseHttpsURI =<< o .: "inbox") <*> (parseHttpsURI =<< o .: "inbox")
<*> o .: "publicKey" <*> (bitraverse parseHttpsURI pure . toEither =<< o .: "publicKey")
instance ToJSON Actor where instance ToJSON Actor where
toJSON = error "toJSON Actor" toJSON = error "toJSON Actor"
@ -181,7 +182,7 @@ instance ToJSON Actor where
<> "type" .= typ <> "type" .= typ
<> "preferredUsername" .= username <> "preferredUsername" .= username
<> "inbox" .= renderURI inbox <> "inbox" .= renderURI inbox
<> "publicKey" .= pkey <> "publicKey" .= fromEither (first renderURI pkey)
-- | This may seem trivial, but it exists for a good reason: In the 'FromJSON' -- | This may seem trivial, but it exists for a good reason: In the 'FromJSON'
-- instance we perform sanity checks. We just don't need to remember the fields -- instance we perform sanity checks. We just don't need to remember the fields
@ -343,12 +344,20 @@ fetchKey manager sigAlgo u = runExceptT $ do
if uriAuthority (publicKeyOwner pkey) == uriAuthority u if uriAuthority (publicKeyOwner pkey) == uriAuthority u
then do then do
actor <- fetch $ publicKeyOwner pkey actor <- fetch $ publicKeyOwner pkey
return (actor, pkey) case actorPublicKey actor of
Left uri ->
if uri == u
then return (actor, pkey)
else throwE "Mismatch between pkey @id and actor publicKey URI"
Right _ -> throwE "Actor publicKey is an object, not the pkey @id URI"
else throwE "Actor and key on different domains, we reject" else throwE "Actor and key on different domains, we reject"
Right' actor -> Right' actor -> do
if actorId actor == u { uriFragment = "" } if actorId actor == u { uriFragment = "" }
then return (actor, actorPublicKey actor) then return ()
else throwE "Actor ID doesn't match the keyid URI we fetched" else throwE "Actor ID doesn't match the keyid URI we fetched"
case actorPublicKey actor of
Left _ -> throwE "keyId resolved to document that has no key"
Right pk -> return (actor, pk)
ExceptT . pure $ do ExceptT . pure $ do
if publicKeyShared pkey if publicKeyShared pkey
then Left "Actor's publicKey is shared, we're rejecting it!" then Left "Actor's publicKey is shared, we're rejecting it!"