feat: Handle HTTP signatures
This commit is contained in:
parent
5529d5f979
commit
30d6284518
15 changed files with 269 additions and 128 deletions
22
README.md
22
README.md
|
|
@ -1,18 +1,8 @@
|
|||
# Postland
|
||||
|
||||
To start your Phoenix server:
|
||||
|
||||
* Run `mix setup` to install and setup dependencies
|
||||
* Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server`
|
||||
|
||||
Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
|
||||
|
||||
Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html).
|
||||
|
||||
## Learn more
|
||||
|
||||
* Official website: https://www.phoenixframework.org/
|
||||
* Guides: https://hexdocs.pm/phoenix/overview.html
|
||||
* Docs: https://hexdocs.pm/phoenix
|
||||
* Forum: https://elixirforum.com/c/phoenix-forum
|
||||
* Source: https://github.com/phoenixframework/phoenix
|
||||
- Posting
|
||||
- Deleting posts
|
||||
- Following
|
||||
- Unfollowing
|
||||
- Liking
|
||||
- Unliking
|
||||
|
|
|
|||
112
lib/activity_pub/headers.ex
Normal file
112
lib/activity_pub/headers.ex
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
defmodule ActivityPub.Headers do
|
||||
alias ActivityPub.Headers.SignatureSplitter
|
||||
|
||||
@http_date_format "%a, %d %b %Y %H:%M:%S GMT"
|
||||
@signing_headers [
|
||||
"(request-target)",
|
||||
"host",
|
||||
"date",
|
||||
"digest"
|
||||
]
|
||||
|
||||
def signing_headers(method, url, body, actor_url, private_key) do
|
||||
method = String.downcase("#{method}")
|
||||
date = DateTime.utc_now() |> Calendar.strftime(@http_date_format)
|
||||
host = url.host
|
||||
digest = :crypto.hash(:sha256, body)
|
||||
digest_header = "SHA-256=#{Base.encode64(digest)}"
|
||||
|
||||
headers =
|
||||
[
|
||||
{"host", host},
|
||||
{"date", date},
|
||||
{"digest", digest_header}
|
||||
]
|
||||
|
||||
to_sign =
|
||||
signing_text(@signing_headers, [request_target_pseudoheader(method, url.path) | headers])
|
||||
|
||||
signature = to_sign |> :public_key.sign(:sha256, private_key) |> Base.encode64()
|
||||
|
||||
signature_header =
|
||||
~s|keyId="#{actor_url}#main-key",headers="(request-target) host date digest",signature="#{signature}"|
|
||||
|
||||
[{"signature", signature_header} | headers]
|
||||
end
|
||||
|
||||
def verify(method, path, headers, actor_fetcher \\ &fetch_actor_key/1) do
|
||||
{_key, signature_header} = Enum.find(headers, fn {key, _} -> key == "signature" end)
|
||||
|
||||
signature_kv = SignatureSplitter.split(signature_header)
|
||||
key_id = find_value(signature_kv, "keyId")
|
||||
signature = signature_kv |> find_value("signature") |> Base.decode64!()
|
||||
signing_text_headers = signature_kv |> find_value("headers") |> String.split(" ")
|
||||
|
||||
to_verify =
|
||||
signing_text(signing_text_headers, [request_target_pseudoheader(method, path) | headers])
|
||||
|
||||
case actor_fetcher.(key_id) do
|
||||
{:ok, public_key} ->
|
||||
:public_key.verify(to_verify, :sha256, signature, public_key)
|
||||
|
||||
error ->
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
def request_target_pseudoheader(method, path) do
|
||||
formatted_method = String.downcase("#{method}")
|
||||
{"(request-target)", "#{formatted_method} #{path}"}
|
||||
end
|
||||
|
||||
def signing_text(signing_text_headers, header_pairs) do
|
||||
signing_text_headers
|
||||
|> Enum.map_join("", fn header_name ->
|
||||
value = find_value(header_pairs, header_name)
|
||||
"#{header_name}: #{value}\n"
|
||||
end)
|
||||
end
|
||||
|
||||
def split_signature_header(signature_header) do
|
||||
signature_header
|
||||
|> String.split(",")
|
||||
|> Enum.map(fn pair_string ->
|
||||
[key, value] = String.split(pair_string, "=")
|
||||
value = String.trim(value, "\"")
|
||||
|
||||
{key, value}
|
||||
end)
|
||||
end
|
||||
|
||||
defp find_value(headers, key) do
|
||||
Enum.find_value(headers, fn {key_candidate, value} ->
|
||||
String.downcase(key_candidate) == key && value
|
||||
end)
|
||||
end
|
||||
|
||||
def fetch_actor_key(key_id) do
|
||||
request =
|
||||
Req.new(url: key_id)
|
||||
|> Req.Request.put_header("accept", "application/json")
|
||||
|
||||
case Req.get(request) do
|
||||
{:ok, result} ->
|
||||
dbg(result)
|
||||
key_map = result.body["publicKey"]
|
||||
|
||||
if key_map["id"] == key_id do
|
||||
[public_key | _] =
|
||||
:public_key.pem_decode(key_map["publicKeyPem"])
|
||||
|> hd()
|
||||
|> :public_key.pem_entry_decode()
|
||||
|
||||
{:ok, public_key}
|
||||
else
|
||||
{:error, :id_mismatch}
|
||||
end
|
||||
|
||||
error ->
|
||||
error
|
||||
end
|
||||
end
|
||||
end
|
||||
30
lib/activity_pub/headers/signature_splitter.ex
Normal file
30
lib/activity_pub/headers/signature_splitter.ex
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
defmodule ActivityPub.Headers.SignatureSplitter do
|
||||
def split(""), do: []
|
||||
|
||||
def split(value) do
|
||||
{key_value, rest} = scan_until(value, ",")
|
||||
{key, value} = split_kv(key_value)
|
||||
|
||||
[{key, value} | split(rest)]
|
||||
end
|
||||
|
||||
defp split_kv(kv_string) do
|
||||
{key, wrapped_value} = scan_until(kv_string, "=")
|
||||
value = String.trim(wrapped_value, ~s("))
|
||||
{key, value}
|
||||
end
|
||||
|
||||
def scan_until(buffer, terminator, acc \\ "")
|
||||
|
||||
def scan_until("", _terminator, acc), do: {acc, ""}
|
||||
|
||||
def scan_until(<<ch::utf8, buffer::binary>>, terminator, acc) do
|
||||
ch = <<ch>>
|
||||
|
||||
if ch == terminator do
|
||||
{acc, buffer}
|
||||
else
|
||||
scan_until(buffer, terminator, acc <> ch)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -30,6 +30,7 @@ defmodule Postland do
|
|||
|
||||
def on_boot_setup() do
|
||||
if !setup?() do
|
||||
# TODO: Require the DB have restrictive permissions
|
||||
%{public: public_key, private: private_key} = generate_keys()
|
||||
|
||||
attrs = %{
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
defmodule PostlandWeb.WebfingerController do
|
||||
use PostlandWeb, :controller
|
||||
|
||||
alias Postland.Accounts
|
||||
|
||||
def get(conn, _params) do
|
||||
render(conn, :webfinger, %{})
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,13 +6,6 @@ defmodule PostlandWeb.UserLoginLive do
|
|||
<div class="mx-auto max-w-sm">
|
||||
<.header class="text-center">
|
||||
Log in to account
|
||||
<:subtitle>
|
||||
Don't have an account?
|
||||
<.link navigate={~p"/users/register"} class="font-semibold text-brand hover:underline">
|
||||
Sign up
|
||||
</.link>
|
||||
for an account now.
|
||||
</:subtitle>
|
||||
</.header>
|
||||
|
||||
<.simple_form for={@form} id="login_form" action={~p"/users/log_in"} phx-update="ignore">
|
||||
|
|
|
|||
4
mix.exs
4
mix.exs
|
|
@ -57,7 +57,9 @@ defmodule Postland.MixProject do
|
|||
{:gettext, "~> 0.20"},
|
||||
{:jason, "~> 1.2"},
|
||||
{:dns_cluster, "~> 0.1.1"},
|
||||
{:bandit, "~> 1.2"}
|
||||
{:bandit, "~> 1.2"},
|
||||
{:req, "~> 0.5.6"},
|
||||
{:stream_data, "~> 1.1.1", only: [:test]}
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
|||
2
mix.lock
2
mix.lock
|
|
@ -35,6 +35,8 @@
|
|||
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
|
||||
"plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"},
|
||||
"plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
|
||||
"req": {:hex, :req, "0.5.6", "8fe1eead4a085510fe3d51ad854ca8f20a622aae46e97b302f499dfb84f726ac", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "cfaa8e720945d46654853de39d368f40362c2641c4b2153c886418914b372185"},
|
||||
"stream_data": {:hex, :stream_data, "1.1.1", "fd515ca95619cca83ba08b20f5e814aaf1e5ebff114659dc9731f966c9226246", [:mix], [], "hexpm", "45d0cd46bd06738463fd53f22b70042dbb58c384bb99ef4e7576e7bb7d3b8c8c"},
|
||||
"swoosh": {:hex, :swoosh, "1.17.1", "01295a82bddd2c6cac1e65856e29444d7c23c4501e0ebc69cea8a82018227e25", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3b20d25e580cb79af631335a1bdcfbffd835c08ebcdc16e98577223a241a18a1"},
|
||||
"tailwind": {:hex, :tailwind, "0.2.3", "277f08145d407de49650d0a4685dc062174bdd1ae7731c5f1da86163a24dfcdb", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "8e45e7a34a676a7747d04f7913a96c770c85e6be810a1d7f91e713d3a3655b5d"},
|
||||
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
|
||||
|
|
|
|||
36
test/activity_pub/headers_test.exs
Normal file
36
test/activity_pub/headers_test.exs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
defmodule ActivityPub.HeadersTest do
|
||||
use Postland.DataCase, async: true
|
||||
|
||||
use ExUnitProperties
|
||||
|
||||
import ActivityPub.Headers
|
||||
|
||||
property "can verify what it signs" do
|
||||
private =
|
||||
Postland.KeyFixtures.private()
|
||||
|> :public_key.pem_decode()
|
||||
|> hd()
|
||||
|> :public_key.pem_entry_decode()
|
||||
|
||||
actor_fetcher = fn _ ->
|
||||
Postland.KeyFixtures.public()
|
||||
|> :public_key.pem_decode()
|
||||
|> hd()
|
||||
|> :public_key.pem_entry_decode()
|
||||
end
|
||||
|
||||
check all(
|
||||
method <- member_of([:get, :post]),
|
||||
body <- StreamData.binary(),
|
||||
host1 <- StreamData.string([?a..?z, ?0..?9, ?-]),
|
||||
host2 <- StreamData.string([?a..?z, ?0..?9, ?-])
|
||||
) do
|
||||
url = URI.parse("#{host1}/inbox")
|
||||
actor_url = "#{host2}/actor"
|
||||
|
||||
headers = signing_headers(method, url, body, actor_url, private)
|
||||
|
||||
verify(method, "/inbox", headers, actor_fetcher)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -57,18 +57,4 @@ defmodule PostlandWeb.UserLoginLiveTest do
|
|||
assert redirected_to(conn) == "/users/log_in"
|
||||
end
|
||||
end
|
||||
|
||||
describe "login navigation" do
|
||||
test "redirects to registration page when the Register button is clicked", %{conn: conn} do
|
||||
{:ok, lv, _html} = live(conn, ~p"/users/log_in")
|
||||
|
||||
{:ok, _login_live, login_html} =
|
||||
lv
|
||||
|> element(~s|main a:fl-contains("Sign up")|)
|
||||
|> render_click()
|
||||
|> follow_redirect(conn, ~p"/users/register")
|
||||
|
||||
assert login_html =~ "Register"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,87 +0,0 @@
|
|||
defmodule PostlandWeb.UserRegistrationLiveTest do
|
||||
use PostlandWeb.ConnCase
|
||||
|
||||
import Phoenix.LiveViewTest
|
||||
import Postland.AccountsFixtures
|
||||
|
||||
describe "Registration page" do
|
||||
test "renders registration page", %{conn: conn} do
|
||||
{:ok, _lv, html} = live(conn, ~p"/users/register")
|
||||
|
||||
assert html =~ "Register"
|
||||
assert html =~ "Log in"
|
||||
end
|
||||
|
||||
test "redirects if already logged in", %{conn: conn} do
|
||||
result =
|
||||
conn
|
||||
|> log_in_user(user_fixture())
|
||||
|> live(~p"/users/register")
|
||||
|> follow_redirect(conn, "/")
|
||||
|
||||
assert {:ok, _conn} = result
|
||||
end
|
||||
|
||||
test "renders errors for invalid data", %{conn: conn} do
|
||||
{:ok, lv, _html} = live(conn, ~p"/users/register")
|
||||
|
||||
result =
|
||||
lv
|
||||
|> element("#registration_form")
|
||||
|> render_change(user: %{"username" => "with spaces", "password" => "too short"})
|
||||
|
||||
assert result =~ "Register"
|
||||
assert result =~ "must contain only letters, numbers, and underscores"
|
||||
assert result =~ "should be at least 12 character"
|
||||
end
|
||||
end
|
||||
|
||||
describe "register user" do
|
||||
test "creates account and logs the user in", %{conn: conn} do
|
||||
{:ok, lv, _html} = live(conn, ~p"/users/register")
|
||||
|
||||
username = unique_user_username()
|
||||
form = form(lv, "#registration_form", user: valid_user_attributes(username: username))
|
||||
render_submit(form)
|
||||
conn = follow_trigger_action(form, conn)
|
||||
|
||||
assert redirected_to(conn) == ~p"/"
|
||||
|
||||
# Now do a logged in request and assert on the menu
|
||||
conn = get(conn, "/")
|
||||
response = html_response(conn, 200)
|
||||
assert response =~ username
|
||||
assert response =~ "Settings"
|
||||
assert response =~ "Log out"
|
||||
end
|
||||
|
||||
test "renders errors for duplicated username", %{conn: conn} do
|
||||
{:ok, lv, _html} = live(conn, ~p"/users/register")
|
||||
|
||||
user = user_fixture(%{username: "test"})
|
||||
|
||||
result =
|
||||
lv
|
||||
|> form("#registration_form",
|
||||
user: %{"username" => user.username, "password" => "valid_password"}
|
||||
)
|
||||
|> render_submit()
|
||||
|
||||
assert result =~ "has already been taken"
|
||||
end
|
||||
end
|
||||
|
||||
describe "registration navigation" do
|
||||
test "redirects to login page when the Log in button is clicked", %{conn: conn} do
|
||||
{:ok, lv, _html} = live(conn, ~p"/users/register")
|
||||
|
||||
{:ok, _login_live, login_html} =
|
||||
lv
|
||||
|> element(~s|main a:fl-contains("Log in")|)
|
||||
|> render_click()
|
||||
|> follow_redirect(conn, ~p"/users/log_in")
|
||||
|
||||
assert login_html =~ "Log in"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -10,7 +10,9 @@ defmodule Postland.AccountsFixtures do
|
|||
def valid_user_attributes(attrs \\ %{}) do
|
||||
Enum.into(attrs, %{
|
||||
username: unique_user_username(),
|
||||
password: valid_user_password()
|
||||
password: valid_user_password(),
|
||||
private_key: "test",
|
||||
public_key: "test"
|
||||
})
|
||||
end
|
||||
|
||||
|
|
|
|||
9
test/support/fixtures/key_fixtures.ex
Normal file
9
test/support/fixtures/key_fixtures.ex
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
defmodule Postland.KeyFixtures do
|
||||
def public do
|
||||
File.read!("test/support/fixtures/public.pem")
|
||||
end
|
||||
|
||||
def private do
|
||||
File.read!("test/support/fixtures/private.pem")
|
||||
end
|
||||
end
|
||||
52
test/support/fixtures/private.pem
Normal file
52
test/support/fixtures/private.pem
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----
|
||||
|
||||
15
test/support/fixtures/public.pem
Normal file
15
test/support/fixtures/public.pem
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
-----BEGIN PUBLIC KEY-----
|
||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5wphw7hrp6UrKZm6Ops3
|
||||
JZmxoAelTH+gWLXGt4+W8j9WzpRU5BuadHqqevgiGEo599bk3ecFlzNY7i7UtnkW
|
||||
jxtykOq6miMbQObW2WhwE/+wj8S9wcH9AjAa34Pz+6YedP6XZ3RbsAj3ON7xo/a8
|
||||
XnDeSwm6hfiYJbkXW83UGeMyzYC4vilM0Ex1zZCGTbJY/FJJyLCikZWxEBtUHuTI
|
||||
J4vwdnfZ6GraQN3l9fLkDAAdyzpG/0EjjhPkEcniHFB8CUVuWXCfaSnRAt3YU2BH
|
||||
yMsqV/mWqHUM5LMvE0q2brN1azT+0sKHBiOAc7VOCDd92603h3MyjmqC3tu82jHQ
|
||||
Wc8wtmEMzwBM6hU+ytKdl/PSud/hiNDATKsogcZLu0NRrrVyvmx4ykmT1DBhyoZy
|
||||
UcEv46iRMY3LturxsBSA65OCr/1ftGbpuaZ3Ill/mWUedQsm1sqPGPX1voNyaC+J
|
||||
5XLhwkqw5McHpmAY7YSx9bXiE06cj/KCDJcE5ZsIjIxu9hGxkWb0OInlWivMXjXu
|
||||
qpFfspEdjl34RFyzb+aX4tnHVz1f4HkeDj94tH1v9njnJsPElJYhwHX8VlGp13rh
|
||||
Z8lpzhqCD5zADby8es2V0RiXgCPKCWt+hyddiJt9hiBQjSFDVYYfeeBrrJu3UV0O
|
||||
KTzdT4N7FjGF4AtYEpJiXCsCAwEAAQ==
|
||||
-----END PUBLIC KEY-----
|
||||
|
||||
Loading…
Reference in a new issue