Merge pull request 'Federation: reject stale and incomplete HTTP signatures' (#8033) from lambadalambda/pleroma:fix/signature-freshness into develop
Reviewed-on: https://git.pleroma.social/pleroma/pleroma/pulls/8033
This commit is contained in:
commit
ca44028928
12 changed files with 563 additions and 59 deletions
|
|
@ -116,6 +116,135 @@ defmodule Pleroma.SignatureTest do
|
|||
end
|
||||
end
|
||||
|
||||
describe "check_signature_policy/2" do
|
||||
@now ~U[2026-10-27 12:00:00Z]
|
||||
|
||||
defp policy_conn(method, signed_headers, extra_headers, extra_params \\ "") do
|
||||
signature =
|
||||
~s(keyId="https://remote.example/users/a#main-key",headers="#{signed_headers}") <>
|
||||
extra_params <> ~s(,signature="fake")
|
||||
|
||||
%Plug.Conn{
|
||||
method: method,
|
||||
req_headers: [{"signature", signature}, {"host", "pleroma.example"} | extra_headers],
|
||||
assigns: %{digest: "SHA-256=abc"}
|
||||
}
|
||||
end
|
||||
|
||||
defp date_header(seconds_ago) do
|
||||
date = DateTime.add(@now, -seconds_ago) |> DateTime.to_naive()
|
||||
{"date", Signature.signed_date(date)}
|
||||
end
|
||||
|
||||
test "accepts a fresh signed GET (Pleroma, Mastodon, Misskey style)" do
|
||||
for headers <- ["(request-target) host date", "(request-target) host date accept"] do
|
||||
conn = policy_conn("GET", headers, [date_header(10)])
|
||||
assert Signature.check_signature_policy(conn, @now) == :ok
|
||||
end
|
||||
end
|
||||
|
||||
test "accepts a fresh signed POST covering the digest" do
|
||||
conn =
|
||||
policy_conn("POST", "(request-target) host content-length digest date", [
|
||||
date_header(10),
|
||||
{"digest", "SHA-256=abc"}
|
||||
])
|
||||
|
||||
assert Signature.check_signature_policy(conn, @now) == :ok
|
||||
end
|
||||
|
||||
test "accepts (created) instead of date (GoToSocial style)" do
|
||||
created = DateTime.to_unix(@now) - 10
|
||||
|
||||
conn =
|
||||
policy_conn(
|
||||
"POST",
|
||||
"(request-target) (created) host digest",
|
||||
[{"digest", "SHA-256=abc"}],
|
||||
",algorithm=\"hs2019\",created=#{created}"
|
||||
)
|
||||
|
||||
assert Signature.check_signature_policy(conn, @now) == :ok
|
||||
end
|
||||
|
||||
test "rejects signatures that cover neither date nor (created)" do
|
||||
conn = policy_conn("GET", "(request-target) host", [date_header(10)])
|
||||
assert Signature.check_signature_policy(conn, @now) == {:error, :date_not_signed}
|
||||
end
|
||||
|
||||
test "rejects a body request whose digest is not signed" do
|
||||
conn =
|
||||
policy_conn("POST", "(request-target) host date", [
|
||||
date_header(10),
|
||||
{"digest", "SHA-256=abc"}
|
||||
])
|
||||
|
||||
assert Signature.check_signature_policy(conn, @now) == {:error, :digest_not_signed}
|
||||
end
|
||||
|
||||
test "rejects a signed date that is missing or unparsable" do
|
||||
conn = policy_conn("GET", "(request-target) host date", [])
|
||||
assert Signature.check_signature_policy(conn, @now) == {:error, :invalid_date}
|
||||
|
||||
conn = policy_conn("GET", "(request-target) host date", [{"date", "yesterday"}])
|
||||
assert Signature.check_signature_policy(conn, @now) == {:error, :invalid_date}
|
||||
end
|
||||
|
||||
test "rejects more than one date header" do
|
||||
conn =
|
||||
policy_conn("GET", "(request-target) host date", [date_header(10), date_header(9000)])
|
||||
|
||||
assert Signature.check_signature_policy(conn, @now) == {:error, :invalid_date}
|
||||
end
|
||||
|
||||
test "rejects a signed digest when the body was not hashed" do
|
||||
conn =
|
||||
policy_conn("POST", "(request-target) host date digest", [
|
||||
date_header(10),
|
||||
{"digest", "SHA-256=abc"}
|
||||
])
|
||||
|> Map.put(:assigns, %{})
|
||||
|
||||
assert Signature.check_signature_policy(conn, @now) == {:error, :body_not_hashed}
|
||||
end
|
||||
|
||||
test "rejects a signed date that is too old or too far in the future" do
|
||||
clear_config([:activitypub, :signature_max_age], 7200)
|
||||
clear_config([:activitypub, :signature_max_clock_skew], 3600)
|
||||
|
||||
conn = policy_conn("GET", "(request-target) host date", [date_header(7200)])
|
||||
assert Signature.check_signature_policy(conn, @now) == :ok
|
||||
|
||||
conn = policy_conn("GET", "(request-target) host date", [date_header(7201)])
|
||||
assert Signature.check_signature_policy(conn, @now) == {:error, :date_too_old}
|
||||
|
||||
conn = policy_conn("GET", "(request-target) host date", [date_header(-3600)])
|
||||
assert Signature.check_signature_policy(conn, @now) == :ok
|
||||
|
||||
conn = policy_conn("GET", "(request-target) host date", [date_header(-3601)])
|
||||
assert Signature.check_signature_policy(conn, @now) == {:error, :date_in_future}
|
||||
end
|
||||
|
||||
test "rejects a (created) timestamp that is too old" do
|
||||
clear_config([:activitypub, :signature_max_age], 60)
|
||||
created = DateTime.to_unix(@now) - 61
|
||||
|
||||
conn =
|
||||
policy_conn("GET", "(request-target) (created) host", [], ",created=#{created}")
|
||||
|
||||
assert Signature.check_signature_policy(conn, @now) == {:error, :date_too_old}
|
||||
end
|
||||
|
||||
test "uses the cavage default of a signed date when headers are not given" do
|
||||
conn = %Plug.Conn{
|
||||
method: "GET",
|
||||
req_headers: [{"signature", "keyId=\"https://remote.example/users/a#main-key\""}]
|
||||
}
|
||||
|
||||
assert Signature.check_signature_policy(conn, @now) == {:error, :invalid_date}
|
||||
end
|
||||
end
|
||||
|
||||
describe "key_id_to_actor_id/1" do
|
||||
test "it properly deduces the actor id for misskey" do
|
||||
assert Signature.key_id_to_actor_id("https://example.com/users/1234/publickey") ==
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
|||
alias Pleroma.Activity
|
||||
alias Pleroma.Delivery
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Signature
|
||||
alias Pleroma.Tests.ObanHelpers
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
|
|
@ -49,6 +50,16 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
|||
|> put_req_header("signature", "keyId=\"#{actor_id}#main-key\"")
|
||||
end
|
||||
|
||||
defp put_fake_signature(conn, actor_id, date \\ Signature.signed_date()) do
|
||||
conn
|
||||
|> put_req_header("date", date)
|
||||
|> put_req_header("digest", "SHA-256=fake-digest")
|
||||
|> put_req_header(
|
||||
"signature",
|
||||
"keyId=\"#{actor_id}#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest content-type\",signature=\"fake-signature\""
|
||||
)
|
||||
end
|
||||
|
||||
defp expect_signature_retry_from(%User{} = signer) do
|
||||
signer_json = UserView.render("user.json", %{user: signer}) |> Map.delete("featured")
|
||||
|
||||
|
|
@ -792,12 +803,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
|||
}
|
||||
}
|
||||
|
||||
Mox.expect(Pleroma.StubbedHTTPSignaturesMock, :validate_conn, fn _conn -> false end)
|
||||
Mox.stub(Pleroma.StubbedHTTPSignaturesMock, :validate_conn, fn _conn -> false end)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> put_req_header("date", "Fri, 29 May 2026 13:33:31 GMT")
|
||||
|> put_req_header("date", Signature.signed_date())
|
||||
|> put_req_header("content-digest", "sha-256=:fake-digest:")
|
||||
|> put_req_header(
|
||||
"signature-input",
|
||||
|
|
@ -832,12 +843,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
|||
legacy_signature =
|
||||
"keyId=\"https://activitypubbot.example/user/ok#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest content-type\",signature=\"fake-signature\""
|
||||
|
||||
Mox.expect(Pleroma.StubbedHTTPSignaturesMock, :validate_conn, fn _conn -> false end)
|
||||
Mox.stub(Pleroma.StubbedHTTPSignaturesMock, :validate_conn, fn _conn -> false end)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> put_req_header("date", "Fri, 29 May 2026 13:33:31 GMT")
|
||||
|> put_req_header("date", Signature.signed_date())
|
||||
|> put_req_header("digest", "SHA-256=fake-digest")
|
||||
|> put_req_header("signature", "sig1=:fake-signature:")
|
||||
|> Map.update!(:req_headers, &[{"signature", legacy_signature} | &1])
|
||||
|
|
@ -871,7 +882,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
|||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> put_req_header("date", "Fri, 29 May 2026 13:33:31 GMT")
|
||||
|> put_req_header("date", Signature.signed_date())
|
||||
|> put_req_header("digest", "SHA-256=fake-digest")
|
||||
|> put_req_header(
|
||||
"signature-input",
|
||||
|
|
@ -881,12 +892,44 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
|||
"signature",
|
||||
"keyId=\"https://activitypubbot.example/user/ok#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest content-type\",signature=\"fake-signature\""
|
||||
)
|
||||
|> post("/inbox", data)
|
||||
|> post("/inbox", Jason.encode!(data))
|
||||
|
||||
assert "ok" == json_response(conn, 200)
|
||||
assert [_] = all_enqueued(worker: SignatureRetryWorker)
|
||||
end
|
||||
|
||||
test "refuses a replayed signed POST instead of queueing a retry", %{conn: conn} do
|
||||
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
|
||||
stale = NaiveDateTime.add(NaiveDateTime.utc_now(), -3 * 3600)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> put_fake_signature(data["actor"], Signature.signed_date(stale))
|
||||
|> post("/inbox", Jason.encode!(data))
|
||||
|
||||
assert "error, invalid HTTP signature (date_too_old)" == json_response(conn, 401)
|
||||
assert [] == all_enqueued(worker: SignatureRetryWorker)
|
||||
assert [] == all_enqueued(worker: ReceiverWorker)
|
||||
end
|
||||
|
||||
test "refuses a signed POST that does not cover the digest", %{conn: conn} do
|
||||
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> put_req_header("date", Signature.signed_date())
|
||||
|> put_req_header(
|
||||
"signature",
|
||||
"keyId=\"#{data["actor"]}#main-key\",headers=\"(request-target) host date\",signature=\"fake\""
|
||||
)
|
||||
|> post("/inbox", Jason.encode!(data))
|
||||
|
||||
assert "error, invalid HTTP signature (digest_not_signed)" == json_response(conn, 401)
|
||||
assert [] == all_enqueued(worker: SignatureRetryWorker)
|
||||
end
|
||||
|
||||
test "does not create a forged post after failed signature retry", %{conn: conn} do
|
||||
alice = insert(:user, local: false, ap_id: "https://one.com/users/alice")
|
||||
bob = insert(:user, local: false, ap_id: "https://two.com/users/bob")
|
||||
|
|
@ -918,8 +961,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
|||
conn
|
||||
|> assign(:valid_signature, false)
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> put_req_header("signature", "keyId=\"https://one.com/users/alice#main-key\"")
|
||||
|> post("/inbox", data)
|
||||
|> put_fake_signature(alice.ap_id)
|
||||
|> post("/inbox", Jason.encode!(data))
|
||||
|
||||
assert "ok" == json_response(conn, 200)
|
||||
|
||||
|
|
@ -950,8 +993,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
|||
conn
|
||||
|> assign(:valid_signature, false)
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> put_req_header("signature", "keyId=\"https://one.com/users/alice#main-key\"")
|
||||
|> post("/inbox", data)
|
||||
|> put_fake_signature(alice.ap_id)
|
||||
|> post("/inbox", Jason.encode!(data))
|
||||
|
||||
assert "ok" == json_response(conn, 200)
|
||||
|
||||
|
|
@ -982,8 +1025,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
|||
conn
|
||||
|> assign(:valid_signature, false)
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> put_req_header("signature", "keyId=\"https://one.com/users/alice#main-key\"")
|
||||
|> post("/inbox", data)
|
||||
|> put_fake_signature(alice.ap_id)
|
||||
|> post("/inbox", Jason.encode!(data))
|
||||
|
||||
assert "ok" == json_response(conn, 200)
|
||||
|
||||
|
|
@ -1025,13 +1068,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
|||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> put_req_header("date", "Thu, 25 Jul 2024 13:33:31 GMT")
|
||||
|> put_req_header("date", Signature.signed_date())
|
||||
|> put_req_header("digest", "SHA-256=fake-digest")
|
||||
|> put_req_header(
|
||||
"signature",
|
||||
"keyId=\"#{alice.ap_id}#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest content-type\",signature=\"fake-signature\""
|
||||
)
|
||||
|> post("/inbox", data)
|
||||
|> post("/inbox", Jason.encode!(data))
|
||||
|
||||
assert conn.assigns.valid_signature == false
|
||||
assert "ok" == json_response(conn, 200)
|
||||
|
|
@ -1063,13 +1106,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
|||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> put_req_header("date", "Thu, 25 Jul 2024 13:33:31 GMT")
|
||||
|> put_req_header("date", Signature.signed_date())
|
||||
|> put_req_header("digest", "SHA-256=fake-digest")
|
||||
|> put_req_header(
|
||||
"signature",
|
||||
"keyId=\"#{alice.ap_id}#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date digest content-type\",signature=\"fake-signature\""
|
||||
)
|
||||
|> post("/inbox", data)
|
||||
|> post("/inbox", Jason.encode!(data))
|
||||
|
||||
assert conn.assigns.valid_signature == false
|
||||
assert "ok" == json_response(conn, 200)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,11 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlugMRFTest do
|
|||
|> expect(:validate_conn, fn _ -> true end)
|
||||
|
||||
build_conn(:get, "/doesntmattter", %{"actor" => actor})
|
||||
|> put_req_header("signature", "keyId=\"#{actor}#main-key")
|
||||
|> put_req_header("date", Pleroma.Signature.signed_date())
|
||||
|> put_req_header(
|
||||
"signature",
|
||||
"keyId=\"#{actor}#main-key\",headers=\"(request-target) host date\",signature=\"fake\""
|
||||
)
|
||||
|> put_format("activity+json")
|
||||
|> HTTPSignaturePlug.call(%{})
|
||||
end
|
||||
|
|
|
|||
119
test/pleroma/web/plugs/http_signature_plug_policy_test.exs
Normal file
119
test/pleroma/web/plugs/http_signature_plug_policy_test.exs
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.Plugs.HTTPSignaturePlugPolicyTest do
|
||||
# Not async: changes global config and uses global HTTP mocks.
|
||||
use Pleroma.Web.ConnCase
|
||||
|
||||
alias Pleroma.Keys
|
||||
alias Pleroma.Object.Fetcher
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.StubbedHTTPSignaturesMock, as: HTTPSignaturesMock
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.InternalFetchActor
|
||||
alias Pleroma.Web.ActivityPub.Publisher
|
||||
alias Pleroma.Web.Plugs.DigestPlug
|
||||
alias Pleroma.Web.Plugs.HTTPSignaturePlug
|
||||
|
||||
import ExUnit.CaptureLog
|
||||
import Mox
|
||||
import Phoenix.Controller, only: [put_format: 2]
|
||||
import Pleroma.Factory
|
||||
import Plug.Conn
|
||||
|
||||
defp incoming_conn(method, url, headers, body \\ "") do
|
||||
%URI{host: host, path: path} = URI.parse(url)
|
||||
|
||||
headers = Enum.map(headers, fn {name, value} -> {String.downcase(name), value} end)
|
||||
|
||||
Plug.Test.conn(method, path, body)
|
||||
|> Map.put(:req_headers, [{"host", host} | headers])
|
||||
|> put_format("activity+json")
|
||||
end
|
||||
|
||||
# Local users have no public_key column set; publish it like a remote copy would.
|
||||
defp store_public_key(%User{} = user) do
|
||||
{:ok, _, public_key} = Keys.keys_from_pem(user.keys)
|
||||
entry = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
|
||||
|
||||
user
|
||||
|> Ecto.Changeset.change(public_key: :public_key.pem_encode([entry]))
|
||||
|> Repo.update!()
|
||||
|> User.set_cache()
|
||||
end
|
||||
|
||||
defp capture_request(method) do
|
||||
test_pid = self()
|
||||
|
||||
Tesla.Mock.mock_global(fn %{method: ^method, headers: headers, body: body} = env ->
|
||||
send(test_pid, {:request, headers, body})
|
||||
%{env | status: 200, body: ""}
|
||||
end)
|
||||
end
|
||||
|
||||
test "accepts deliveries signed by Pleroma's publisher" do
|
||||
stub_with(Pleroma.Web.ActivityPub.TransmogrifierMock, Pleroma.Web.ActivityPub.Transmogrifier)
|
||||
capture_request(:post)
|
||||
|
||||
activity = insert(:note_activity)
|
||||
store_public_key(User.get_by_ap_id(activity.data["actor"]))
|
||||
inbox = "https://remote.example/inbox"
|
||||
|
||||
{:ok, _} =
|
||||
%{inbox: inbox, activity_id: activity.id}
|
||||
|> Publisher.prepare_one()
|
||||
|> Publisher.publish_one()
|
||||
|
||||
assert_received {:request, headers, body}
|
||||
|
||||
{:ok, _, conn} =
|
||||
incoming_conn(:post, inbox, [{"content-length", "#{byte_size(body)}"} | headers], body)
|
||||
|> DigestPlug.read_body([])
|
||||
|
||||
conn = HTTPSignaturePlug.call(conn, %{})
|
||||
|
||||
assert conn.assigns.valid_signature == true
|
||||
refute conn.halted
|
||||
end
|
||||
|
||||
test "accepts object fetches signed by Pleroma's fetcher" do
|
||||
clear_config([:activitypub, :sign_object_fetches], true)
|
||||
capture_request(:get)
|
||||
store_public_key(InternalFetchActor.get_actor())
|
||||
|
||||
url = "https://remote.example/objects/1"
|
||||
Fetcher.fetch_and_contain_remote_object_from_id(url)
|
||||
|
||||
assert_received {:request, headers, _body}
|
||||
|
||||
conn =
|
||||
incoming_conn(:get, url, headers)
|
||||
|> HTTPSignaturePlug.call(%{})
|
||||
|
||||
assert conn.assigns.valid_signature == true
|
||||
end
|
||||
|
||||
test "only logs policy violations when the policy is set to :log" do
|
||||
clear_config([:activitypub, :signature_policy], :log)
|
||||
|
||||
HTTPSignaturesMock
|
||||
|> expect(:validate_conn, fn _ -> true end)
|
||||
|
||||
log =
|
||||
capture_log(fn ->
|
||||
conn =
|
||||
incoming_conn(:post, "https://pleroma.example/inbox", [
|
||||
{"date", Pleroma.Signature.signed_date()},
|
||||
{"signature",
|
||||
"keyId=\"https://remote.example/users/a#main-key\",headers=\"(request-target) host date\",signature=\"fake\""}
|
||||
])
|
||||
|> HTTPSignaturePlug.call(%{})
|
||||
|
||||
assert conn.assigns.valid_signature == true
|
||||
refute conn.halted
|
||||
end)
|
||||
|
||||
assert log =~ "digest_not_signed"
|
||||
end
|
||||
end
|
||||
|
|
@ -13,6 +13,15 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlugTest do
|
|||
import Phoenix.Controller, only: [put_format: 2]
|
||||
import Plug.Conn
|
||||
|
||||
defp put_signature(conn, actor, signed_headers \\ "(request-target) host date") do
|
||||
conn
|
||||
|> put_req_header("date", Pleroma.Signature.signed_date())
|
||||
|> put_req_header(
|
||||
"signature",
|
||||
"keyId=\"#{actor}#main-key\",headers=\"#{signed_headers}\",signature=\"fake\""
|
||||
)
|
||||
end
|
||||
|
||||
test "it calls HTTPSignatures to check validity if the actor signed it" do
|
||||
params = %{"actor" => "http://mastodon.example.org/users/admin"}
|
||||
conn = build_conn(:get, "/doesntmattter", params)
|
||||
|
|
@ -22,10 +31,7 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlugTest do
|
|||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header(
|
||||
"signature",
|
||||
"keyId=\"http://mastodon.example.org/users/admin#main-key"
|
||||
)
|
||||
|> put_signature("http://mastodon.example.org/users/admin")
|
||||
|> put_format("activity+json")
|
||||
|> HTTPSignaturePlug.call(%{})
|
||||
|
||||
|
|
@ -51,10 +57,7 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlugTest do
|
|||
|
||||
conn =
|
||||
orig_conn
|
||||
|> put_req_header(
|
||||
"signature",
|
||||
"keyId=\"http://mastodon.example.org/users/admin#main-key"
|
||||
)
|
||||
|> put_signature("http://mastodon.example.org/users/admin")
|
||||
|> HTTPSignaturePlug.call(%{})
|
||||
|
||||
assert conn.assigns.valid_signature == false
|
||||
|
|
@ -71,10 +74,7 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlugTest do
|
|||
|
||||
conn =
|
||||
orig_conn
|
||||
|> put_req_header(
|
||||
"signature",
|
||||
"keyId=\"http://mastodon.example.org/users/admin#main-key"
|
||||
)
|
||||
|> put_signature("http://mastodon.example.org/users/admin")
|
||||
|> HTTPSignaturePlug.call(%{})
|
||||
|
||||
assert conn.assigns.valid_signature == true
|
||||
|
|
@ -108,10 +108,7 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlugTest do
|
|||
conn =
|
||||
conn
|
||||
|> Map.put(:remote_ip, {192, 168, 0, 1})
|
||||
|> put_req_header(
|
||||
"signature",
|
||||
"keyId=\"http://mastodon.example.org/users/admin#main-key"
|
||||
)
|
||||
|> put_signature("http://mastodon.example.org/users/admin")
|
||||
|> HTTPSignaturePlug.call(%{})
|
||||
|
||||
assert conn.remote_ip == {192, 168, 0, 1}
|
||||
|
|
@ -131,10 +128,7 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlugTest do
|
|||
|
||||
conn =
|
||||
build_conn(:get, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"})
|
||||
|> put_req_header(
|
||||
"signature",
|
||||
"keyId=\"http://mastodon.example.org/users/admin#main-key"
|
||||
)
|
||||
|> put_signature("http://mastodon.example.org/users/admin")
|
||||
|> put_format("activity+json")
|
||||
|> HTTPSignaturePlug.call(%{})
|
||||
|
||||
|
|
@ -152,14 +146,84 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlugTest do
|
|||
|
||||
conn =
|
||||
build_conn(:get, "/doesntmattter", %{"actor" => "http://allowed.example.org/users/admin"})
|
||||
|> put_req_header(
|
||||
"signature",
|
||||
"keyId=\"http://allowed.example.org/users/admin#main-key"
|
||||
)
|
||||
|> put_signature("http://allowed.example.org/users/admin")
|
||||
|> put_format("activity+json")
|
||||
|> HTTPSignaturePlug.call(%{})
|
||||
|
||||
assert conn.assigns.valid_signature == true
|
||||
assert conn.halted == false
|
||||
end
|
||||
|
||||
describe "signature policy" do
|
||||
test "treats a signed GET with a stale date as unsigned" do
|
||||
HTTPSignaturesMock
|
||||
|> expect(:validate_conn, 0, fn _ -> true end)
|
||||
|
||||
stale = NaiveDateTime.add(NaiveDateTime.utc_now(), -3 * 3600)
|
||||
|
||||
conn =
|
||||
build_conn(:get, "/doesntmattter")
|
||||
|> put_signature("http://mastodon.example.org/users/admin")
|
||||
|> put_req_header("date", Pleroma.Signature.signed_date(stale))
|
||||
|> put_format("activity+json")
|
||||
|> HTTPSignaturePlug.call(%{})
|
||||
|
||||
assert conn.assigns.valid_signature == false
|
||||
assert conn.assigns.signature_error == :date_too_old
|
||||
refute conn.halted
|
||||
end
|
||||
|
||||
test "marks a signed POST that does not cover the digest as invalid" do
|
||||
HTTPSignaturesMock
|
||||
|> expect(:validate_conn, 0, fn _ -> true end)
|
||||
|
||||
conn =
|
||||
build_conn(:post, "/inbox")
|
||||
|> put_signature("http://mastodon.example.org/users/admin")
|
||||
|> put_format("activity+json")
|
||||
|> HTTPSignaturePlug.call(%{})
|
||||
|
||||
assert conn.assigns.valid_signature == false
|
||||
assert conn.assigns.signature_error == :digest_not_signed
|
||||
refute conn.halted
|
||||
end
|
||||
|
||||
test "uses the digest computed from the body for signed POSTs" do
|
||||
HTTPSignaturesMock
|
||||
|> expect(:validate_conn, fn conn ->
|
||||
get_req_header(conn, "digest") == ["SHA-256=computed"]
|
||||
end)
|
||||
|
||||
conn =
|
||||
build_conn(:post, "/inbox")
|
||||
|> put_signature(
|
||||
"http://mastodon.example.org/users/admin",
|
||||
"(request-target) host date digest"
|
||||
)
|
||||
|> put_req_header("digest", "SHA-256=claimed")
|
||||
|> assign(:digest, "SHA-256=computed")
|
||||
|> put_format("activity+json")
|
||||
|> HTTPSignaturePlug.call(%{})
|
||||
|
||||
assert conn.assigns.valid_signature == true
|
||||
end
|
||||
|
||||
test "marks a signed POST whose body was not hashed as invalid" do
|
||||
HTTPSignaturesMock
|
||||
|> expect(:validate_conn, 0, fn _ -> true end)
|
||||
|
||||
conn =
|
||||
build_conn(:post, "/inbox")
|
||||
|> put_signature(
|
||||
"http://mastodon.example.org/users/admin",
|
||||
"(request-target) host date digest"
|
||||
)
|
||||
|> put_req_header("digest", "SHA-256=claimed")
|
||||
|> put_format("activity+json")
|
||||
|> HTTPSignaturePlug.call(%{})
|
||||
|
||||
assert conn.assigns.valid_signature == false
|
||||
assert conn.assigns.signature_error == :body_not_hashed
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue