signature: refetch key upon verification failure

This matches behaviour prioir to the SigningKey migration
and the expected semantics of the http_signatures lib.
Additionally add a min interval paramter, to avoid
refetch floods on bugs causing incompatible signatures
(like e.g. currently with Bridgy)
This commit is contained in:
Oneric 2025-01-13 00:24:01 +01:00
parent a7b4e4bfd9
commit 9cc5fe9a5f
4 changed files with 38 additions and 3 deletions

View file

@ -370,6 +370,7 @@
note_replies_output_limit: 5,
sign_object_fetches: true,
authorized_fetch_mode: false,
min_key_refetch_interval: 86_400,
max_collection_objects: 50
config :pleroma, :streamer,

View file

@ -27,12 +27,18 @@ def fetch_public_key(conn) do
def refetch_public_key(conn) do
with {_, %{"keyId" => kid}} <- {:keyid, HTTPSignatures.signature_for_conn(conn)},
# TODO: force a refetch of stale keys (perhaps with a backoff time based on updated_at)
{_, {:ok, %SigningKey{} = sk}, _} <-
{:fetch, SigningKey.get_or_fetch_by_key_id(kid), kid},
{_, {:ok, %SigningKey{} = sk}, _} <- {:fetch, SigningKey.refresh_by_key_id(kid), kid},
{_, {:ok, decoded_key}} <- {:decode, SigningKey.public_key_decoded(sk)} do
{:ok, decoded_key}
else
{:fetch, {:error, :too_young}, kid} ->
Logger.debug("Refusing to refetch recently updated key: #{kid}")
{:error, {:fetch, :too_young}}
{:fetch, {:error, :unknown}, kid} ->
Logger.warning("Attempted to refresh unknown key; this should not happen: #{kid}")
{:error, {:fetch, :unknown}}
{:fetch, error, kid} ->
Logger.error("Failed to refresh stale key from signature: #{kid} #{inspect(error)}")
{:error, {:fetch, error}}

View file

@ -218,6 +218,23 @@ def fetch_remote_key(key_id) do
end
end
defp refresh_key(%__MODULE__{} = key) do
min_backoff = Pleroma.Config.get!([:activitypub, :min_key_refetch_interval])
if Timex.diff(Timex.now(), key.updated_at, :seconds) >= min_backoff do
fetch_remote_key(key.key_id)
else
{:error, :too_young}
end
end
def refresh_by_key_id(key_id) do
case Repo.get_by(__MODULE__, key_id: key_id) do
nil -> {:error, :unknown}
key -> refresh_key(key)
end
end
# Take the response from the remote instance and extract the key details
# will check if the key ID matches the owner of the key, if not, error
defp extract_key_details(%{"id" => ap_id, "publicKey" => public_key}) do

View file

@ -56,8 +56,19 @@ test "it returns error if public key is nil" do
describe "refetch_public_key/1" do
test "it returns key" do
clear_config([:activitypub, :min_key_refetch_interval], 0)
ap_id = "https://mastodon.social/users/lambadalambda"
%Pleroma.User{signing_key: sk} =
Pleroma.User.get_or_fetch_by_ap_id(ap_id)
|> then(fn {:ok, u} -> u end)
|> Pleroma.User.SigningKey.load_key()
{:ok, _} =
%{sk | public_key: "-----BEGIN PUBLIC KEY-----\nasdfghjkl"}
|> Ecto.Changeset.change()
|> Pleroma.Repo.update()
assert Signature.refetch_public_key(make_fake_conn(ap_id)) == {:ok, @rsa_public_key}
end
end