1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
7 import Phoenix.Controller, only: [get_format: 1, text: 2]
9 alias Pleroma.Web.Router
10 alias Pleroma.Signature
11 alias Pleroma.Instances
14 @cachex Pleroma.Config.get([:cachex, :provider], Cachex)
20 def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
24 def call(conn, _opts) do
25 if get_format(conn) == "activity+json" do
27 |> maybe_assign_valid_signature()
28 |> maybe_require_signature()
34 def route_aliases(%{path_info: ["objects", id], query_string: query_string}) do
35 ap_id = Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :object, id)
37 with %Activity{} = activity <- Activity.get_by_object_ap_id_with_object(ap_id) do
38 ["/notice/#{activity.id}", "/notice/#{activity.id}?#{query_string}"]
44 def route_aliases(_), do: []
46 defp assign_valid_signature_on_route_aliases(conn, []), do: conn
48 defp assign_valid_signature_on_route_aliases(%{assigns: %{valid_signature: true}} = conn, _),
51 defp assign_valid_signature_on_route_aliases(conn, [path | rest]) do
52 request_target = String.downcase("#{conn.method}") <> " #{path}"
56 |> put_req_header("(request-target)", request_target)
58 %{assigns: %{digest: digest}} = conn -> put_req_header(conn, "digest", digest)
63 |> assign(:valid_signature, HTTPSignatures.validate_conn(conn))
64 |> assign(:signature_actor_id, signature_host(conn))
65 |> assign_valid_signature_on_route_aliases(rest)
68 defp maybe_assign_valid_signature(conn) do
69 if has_signature_header?(conn) do
70 # set (request-target) header to the appropriate value
71 # we also replace the digest header with the one we computed
73 route_aliases(conn) ++ [conn.request_path, conn.request_path <> "?#{conn.query_string}"]
75 assign_valid_signature_on_route_aliases(conn, possible_paths)
77 Logger.debug("No signature header!")
82 defp has_signature_header?(conn) do
83 conn |> get_req_header("signature") |> Enum.at(0, false)
86 defp maybe_require_signature(
87 %{assigns: %{valid_signature: true, signature_actor_id: actor_id}} = conn
89 # inboxes implicitly need http signatures for authentication
90 # so we don't really know if the instance will have broken federation after
91 # we turn on authorized_fetch_mode.
93 # to "check" this is a signed fetch, verify if method is GET
94 if conn.method == "GET" do
95 actor_host = URI.parse(actor_id).host
97 case @cachex.get(:request_signatures_cache, actor_host) do
99 Logger.debug("Successful signature from #{actor_host}")
100 Instances.set_request_signatures(actor_host)
101 @cachex.put(:request_signatures_cache, actor_host, true)
108 "expected request signature cache to return a boolean, instead got #{inspect(any)}"
116 defp maybe_require_signature(%{assigns: %{valid_signature: true}} = conn), do: conn
118 defp maybe_require_signature(conn) do
119 if Pleroma.Config.get([:activitypub, :authorized_fetch_mode], false) do
121 |> put_status(:unauthorized)
122 |> text("Request not signed")
129 defp signature_host(conn) do
130 with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
131 {:ok, actor_id} <- Signature.key_id_to_actor_id(kid) do