diff --git a/lib/nulla/http_signature.ex b/lib/nulla/http_signature.ex index e32e5b2..5207c2d 100644 --- a/lib/nulla/http_signature.ex +++ b/lib/nulla/http_signature.ex @@ -7,12 +7,11 @@ defmodule Nulla.HTTPSignature do date = DateTime.utc_now() |> Calendar.strftime("%a, %d %b %Y %H:%M:%S GMT") uri = URI.parse(inbox_url) - signature_string = """ - (request-target): post #{uri.path} - host: #{uri.host} - date: #{date} - digest: #{digest} - """ + signature_string = + "(request-target): post #{uri.path}\n" <> + "host: #{uri.host}\n" <> + "date: #{date}\n" <> + "digest: #{digest}" user = User.get_user(id: actor.id) @@ -56,13 +55,16 @@ defmodule Nulla.HTTPSignature do end defp parse_signature_header(header) do + header = + header + |> String.split(",") + |> Enum.map(fn pair -> + [k, v] = String.split(pair, "=", parts: 2) + {String.trim(k), String.trim(v, ~s("))} + end) + |> Enum.into(%{}) + header - |> String.split(",") - |> Enum.map(fn pair -> - [k, v] = String.split(pair, "=", parts: 2) - {String.trim(k), String.trim(v, ~s("))} - end) - |> Enum.into(%{}) end defp build_signature_string(nil, _conn), do: {:error, :missing_headers} @@ -83,6 +85,9 @@ defmodule Nulla.HTTPSignature do "(request-target): #{method} #{path}" + "host" -> + "host: #{conn.host}" + _ -> value = get_req_header(conn, header) |> List.first() if value, do: "#{header}: #{value}", else: nil diff --git a/test/nulla/http_signature_test.exs b/test/nulla/http_signature_test.exs new file mode 100644 index 0000000..2983a17 --- /dev/null +++ b/test/nulla/http_signature_test.exs @@ -0,0 +1,40 @@ +defmodule Nulla.HTTPSignatureTest do + use NullaWeb.ConnCase, async: false + import Plug.Conn + import Plug.Test + import Nulla.Fixtures.Data + alias Nulla.HTTPSignature + alias Nulla.Snowflake + alias Nulla.Models.Actor + + setup do + create_data() + :ok + end + + test "make_headers/3 creates valid signature headers and verify/2 validates them" do + actor = Actor.get_actor(preferredUsername: "test") + target_actor = Actor.get_actor(preferredUsername: "test2") + + follow_activity = %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "id" => Snowflake.next_id(), + "type" => "Follow", + "actor" => actor.ap_id, + "object" => target_actor.ap_id + } + + body = Jason.encode!(follow_activity) + headers = HTTPSignature.make_headers(body, target_actor.inbox, actor) + + conn = + conn(:post, "/users/test2/inbox", body) + |> put_req_header("content-type", Map.new(headers) |> Map.get("Content-Type")) + |> put_req_header("date", Map.new(headers) |> Map.get("Date")) + |> put_req_header("digest", Map.new(headers) |> Map.get("Digest")) + |> put_req_header("signature", Map.new(headers) |> Map.get("Signature")) + |> Map.put(:host, URI.parse(target_actor.inbox).host) + + assert :ok = HTTPSignature.verify(conn, actor.publicKey["publicKeyPem"]) + end +end diff --git a/test/nulla/keys.exs b/test/nulla/keys.exs new file mode 100644 index 0000000..10f8083 --- /dev/null +++ b/test/nulla/keys.exs @@ -0,0 +1,32 @@ +defmodule Nulla.KeysTest do + use NullaWeb.ConnCase, async: false + import Nulla.Fixtures.Data + alias Nulla.Models.User + alias Nulla.Models.Actor + + setup do + create() + :ok + end + + test "verify user's keys" do + actor = Actor.get_actor(preferredUsername: "test") + user = User.get_user(id: actor.id) + + message = "test message" + + private_key = + :public_key.pem_decode(user.privateKeyPem) + |> hd() + |> :public_key.pem_entry_decode() + + public_key = + :public_key.pem_decode(actor.publicKey["publicKeyPem"]) + |> hd() + |> :public_key.pem_entry_decode() + + signature = :public_key.sign(message, :sha256, private_key) + + assert :public_key.verify(message, :sha256, signature, public_key) + end +end diff --git a/test/nulla_web/controllers/actor_controller_test.exs b/test/nulla_web/controllers/actor_controller_test.exs index e2af38c..4204cb1 100644 --- a/test/nulla_web/controllers/actor_controller_test.exs +++ b/test/nulla_web/controllers/actor_controller_test.exs @@ -1,8 +1,9 @@ defmodule NullaWeb.ActorControllerTest do use NullaWeb.ConnCase + import Nulla.Fixtures.Data setup do - Nulla.Fixtures.Data.create() + create_data() :ok end diff --git a/test/nulla_web/controllers/follow_controller_test.exs b/test/nulla_web/controllers/follow_controller_test.exs index b285e7f..711148d 100644 --- a/test/nulla_web/controllers/follow_controller_test.exs +++ b/test/nulla_web/controllers/follow_controller_test.exs @@ -1,8 +1,9 @@ defmodule NullaWeb.FollowControllerTest do use NullaWeb.ConnCase + import Nulla.Fixtures.Data setup do - Nulla.Fixtures.Data.create() + create_data() :ok end diff --git a/test/nulla_web/controllers/inbox_controller_test.exs b/test/nulla_web/controllers/inbox_controller_test.exs index 57dea6b..14e2559 100644 --- a/test/nulla_web/controllers/inbox_controller_test.exs +++ b/test/nulla_web/controllers/inbox_controller_test.exs @@ -1,11 +1,12 @@ defmodule NullaWeb.InboxControllerTest do use NullaWeb.ConnCase + import Nulla.Fixtures.Data alias Nulla.Snowflake alias Nulla.Models.User alias Nulla.Models.Actor setup do - Nulla.Fixtures.Data.create() + create_data() :ok end diff --git a/test/nulla_web/controllers/nodeinfo_controller_test.exs b/test/nulla_web/controllers/nodeinfo_controller_test.exs index 1fd570f..ab1d5d2 100644 --- a/test/nulla_web/controllers/nodeinfo_controller_test.exs +++ b/test/nulla_web/controllers/nodeinfo_controller_test.exs @@ -1,8 +1,9 @@ defmodule NullaWeb.NodeinfoControllerTest do use NullaWeb.ConnCase + import Nulla.Fixtures.Data setup do - Nulla.Fixtures.Data.create() + create_data() :ok end diff --git a/test/nulla_web/controllers/note_controller_test.exs b/test/nulla_web/controllers/note_controller_test.exs index d094f83..16c328b 100644 --- a/test/nulla_web/controllers/note_controller_test.exs +++ b/test/nulla_web/controllers/note_controller_test.exs @@ -1,10 +1,11 @@ defmodule NullaWeb.NoteControllerTest do use NullaWeb.ConnCase + import Nulla.Fixtures.Data alias Nulla.Models.Actor alias Nulla.Models.Note setup do - Nulla.Fixtures.Data.create() + create_data() :ok end diff --git a/test/nulla_web/controllers/outbox_controller_test.exs b/test/nulla_web/controllers/outbox_controller_test.exs index e8d0bd2..0318e96 100644 --- a/test/nulla_web/controllers/outbox_controller_test.exs +++ b/test/nulla_web/controllers/outbox_controller_test.exs @@ -1,8 +1,9 @@ defmodule NullaWeb.OutboxControllerTest do use NullaWeb.ConnCase + import Nulla.Fixtures.Data setup do - Nulla.Fixtures.Data.create() + create_data() :ok end diff --git a/test/nulla_web/controllers/webfinger_controller_test.exs b/test/nulla_web/controllers/webfinger_controller_test.exs index b0f70c7..7c7cc18 100644 --- a/test/nulla_web/controllers/webfinger_controller_test.exs +++ b/test/nulla_web/controllers/webfinger_controller_test.exs @@ -1,8 +1,9 @@ defmodule NullaWeb.WebfingerControllerTest do use NullaWeb.ConnCase + import Nulla.Fixtures.Data setup do - Nulla.Fixtures.Data.create() + create_data() :ok end diff --git a/test/support/fixtures/data.ex b/test/support/fixtures/data.ex index 1327dca..8e40b58 100644 --- a/test/support/fixtures/data.ex +++ b/test/support/fixtures/data.ex @@ -4,30 +4,24 @@ defmodule Nulla.Fixtures.Data do alias Nulla.Models.Actor alias Nulla.Models.Note - def create do - endpoint_config = Application.fetch_env!(:nulla, NullaWeb.Endpoint) - ip = endpoint_config[:http][:ip] - host = :inet_parse.ntoa(ip) |> to_string() - port = endpoint_config[:http][:port] - base_url = "http://#{host}:#{port}" - + def create_data do {publicKeyPem, privateKeyPem} = KeyGen.gen() {:ok, actor} = Actor.create_actor(%{ domain: "localhost", - ap_id: "#{base_url}/users/test", + ap_id: "http://localhost/users/test", type: "Person", - following: "#{base_url}/users/test/following", - followers: "#{base_url}/users/test/followers", - inbox: "#{base_url}/users/test/inbox", - outbox: "#{base_url}/users/test/outbox", - featured: "#{base_url}/users/test/collections/featured", - featuredTags: "#{base_url}/users/test/collections/tags", + following: "http://localhost/users/test/following", + followers: "http://localhost/users/test/followers", + inbox: "http://localhost/users/test/inbox", + outbox: "http://localhost/users/test/outbox", + featured: "http://localhost/users/test/collections/featured", + featuredTags: "http://localhost/users/test/collections/tags", preferredUsername: "test", name: "Test", summary: "Test User", - url: "#{base_url}/@test", + url: "http://localhost/@test", manuallyApprovesFollowers: false, discoverable: true, indexable: true, @@ -35,11 +29,11 @@ defmodule Nulla.Fixtures.Data do memorial: false, publicKey: Jason.OrderedObject.new( - id: "#{base_url}/users/test#main-key", - owner: "#{base_url}/users/test", + id: "http://localhost/users/test#main-key", + owner: "http://localhost/users/test", publicKeyPem: publicKeyPem ), - endpoints: Jason.OrderedObject.new(sharedInbox: "#{base_url}/inbox") + endpoints: Jason.OrderedObject.new(sharedInbox: "http://localhost/inbox") }) User.create_user(%{ @@ -62,18 +56,18 @@ defmodule Nulla.Fixtures.Data do {:ok, actor} = Actor.create_actor(%{ domain: "localhost", - ap_id: "#{base_url}/users/test2", + ap_id: "http://localhost/users/test2", type: "Person", - following: "#{base_url}/users/test2/following", - followers: "#{base_url}/users/test2/followers", - inbox: "#{base_url}/users/test2/inbox", - outbox: "#{base_url}/users/test2/outbox", - featured: "#{base_url}/users/test2/collections/featured", - featuredTags: "#{base_url}/users/test2/collections/tags", + following: "http://localhost/users/test2/following", + followers: "http://localhost/users/test2/followers", + inbox: "http://localhost/users/test2/inbox", + outbox: "http://localhost/users/test2/outbox", + featured: "http://localhost/users/test2/collections/featured", + featuredTags: "http://localhost/users/test2/collections/tags", preferredUsername: "test2", name: "Test", summary: "Test User", - url: "#{base_url}/@test2", + url: "http://localhost/@test2", manuallyApprovesFollowers: false, discoverable: true, indexable: true, @@ -81,11 +75,11 @@ defmodule Nulla.Fixtures.Data do memorial: false, publicKey: Jason.OrderedObject.new( - id: "#{base_url}/users/test2#main-key", - owner: "#{base_url}/users/test2", + id: "http://localhost/users/test2#main-key", + owner: "http://localhost/users/test2", publicKeyPem: publicKeyPem ), - endpoints: Jason.OrderedObject.new(sharedInbox: "#{base_url}/inbox") + endpoints: Jason.OrderedObject.new(sharedInbox: "http://localhost/inbox") }) User.create_user(%{