diff --git a/CHANGELOG.md b/CHANGELOG.md index 654d208dd..6f268d110 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Addressable lists - Twitter API: added rate limit for `/api/account/password_reset` endpoint. - ActivityPub: Add an internal service actor for fetching ActivityPub objects. +- ActivityPub: Optional signing of ActivityPub object fetches. ### Changed - Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text diff --git a/config/config.exs b/config/config.exs index 38780eef7..bda92e45d 100644 --- a/config/config.exs +++ b/config/config.exs @@ -305,7 +305,8 @@ accept_blocks: true, unfollow_blocked: true, outgoing_blocks: true, - follow_handshake_timeout: 500 + follow_handshake_timeout: 500, + sign_object_fetches: true config :pleroma, :user, deny_follow_blocked: true diff --git a/config/test.exs b/config/test.exs index 0af62aa14..92dca18bc 100644 --- a/config/test.exs +++ b/config/test.exs @@ -31,6 +31,8 @@ skip_thread_containment: false, federating: false +config :pleroma, :activitypub, sign_object_fetches: false + # Configure your database config :pleroma, Pleroma.Repo, adapter: Ecto.Adapters.Postgres, diff --git a/docs/config.md b/docs/config.md index 42556a0be..02f86dc16 100644 --- a/docs/config.md +++ b/docs/config.md @@ -332,6 +332,7 @@ This will make Pleroma listen on `127.0.0.1` port `8080` and generate urls start * ``unfollow_blocked``: Whether blocks result in people getting unfollowed * ``outgoing_blocks``: Whether to federate blocks to other instances * ``deny_follow_blocked``: Whether to disallow following an account that has blocked the user in question +* ``sign_object_fetches``: Sign object fetches with HTTP signatures ## :http_security * ``enabled``: Whether the managed content security policy is enabled diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 96b34ae9f..305ce8357 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -6,6 +6,8 @@ defmodule Pleroma.Object.Fetcher do alias Pleroma.HTTP alias Pleroma.Object alias Pleroma.Object.Containment + alias Pleroma.Signature + alias Pleroma.Web.ActivityPub.InternalFetchActor alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.OStatus @@ -82,15 +84,52 @@ def fetch_object_from_id!(id, options \\ []) do end end + defp make_signature(id, date) do + uri = URI.parse(id) + + signature = + InternalFetchActor.get_actor() + |> Signature.sign(%{ + "(request-target)": "get #{uri.path}", + host: uri.host, + date: date + }) + + [{:Signature, signature}] + end + + defp sign_fetch(headers, id, date) do + if Pleroma.Config.get([:activitypub, :sign_object_fetches]) do + headers ++ make_signature(id, date) + else + headers + end + end + + defp maybe_date_fetch(headers, date) do + if Pleroma.Config.get([:activitypub, :sign_object_fetches]) do + headers ++ [{:Date, date}] + else + headers + end + end + def fetch_and_contain_remote_object_from_id(id) do Logger.info("Fetching object #{id} via AP") + date = + NaiveDateTime.utc_now() + |> Timex.format!("{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT") + + headers = + [{:Accept, "application/activity+json"}] + |> maybe_date_fetch(date) + |> sign_fetch(id, date) + + Logger.debug("Fetch headers: #{inspect(headers)}") + with true <- String.starts_with?(id, "http"), - {:ok, %{body: body, status: code}} when code in 200..299 <- - HTTP.get( - id, - [{:Accept, "application/activity+json"}] - ), + {:ok, %{body: body, status: code}} when code in 200..299 <- HTTP.get(id, headers), {:ok, data} <- Jason.decode(body), :ok <- Containment.contain_origin_from_id(id, data) do {:ok, data} diff --git a/test/object/fetcher_test.exs b/test/object/fetcher_test.exs index 56a9d775f..482252cff 100644 --- a/test/object/fetcher_test.exs +++ b/test/object/fetcher_test.exs @@ -150,4 +150,34 @@ test "it can refetch pruned objects" do assert object.id != object_two.id end end + + describe "signed fetches" do + test_with_mock "it signs fetches when configured to do so", + Pleroma.Signature, + [:passthrough], + [] do + option = Pleroma.Config.get([:activitypub, :sign_object_fetches]) + Pleroma.Config.put([:activitypub, :sign_object_fetches], true) + + Fetcher.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367") + + assert called(Pleroma.Signature.sign(:_, :_)) + + Pleroma.Config.put([:activitypub, :sign_object_fetches], option) + end + + test_with_mock "it doesn't sign fetches when not configured to do so", + Pleroma.Signature, + [:passthrough], + [] do + option = Pleroma.Config.get([:activitypub, :sign_object_fetches]) + Pleroma.Config.put([:activitypub, :sign_object_fetches], false) + + Fetcher.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367") + + refute called(Pleroma.Signature.sign(:_, :_)) + + Pleroma.Config.put([:activitypub, :sign_object_fetches], option) + end + end end