diff --git a/lib/nulla/httpsignature.ex b/lib/nulla/httpsignature.ex index 9183604..dad2d7f 100644 --- a/lib/nulla/httpsignature.ex +++ b/lib/nulla/httpsignature.ex @@ -1,11 +1,10 @@ defmodule Nulla.HTTPSignature do import Plug.Conn - def verify(conn, actor_json) do + def verify(conn, public_key_pem) do with [sig_header] <- get_req_header(conn, "signature"), signature_map <- parse_signature_header(sig_header), {:ok, signed_string} <- build_signature_string(signature_map["headers"], conn), - {:ok, public_key_pem} <- extract_public_key(actor_json), true <- verify_signature(public_key_pem, signed_string, signature_map["signature"]) do :ok else @@ -54,9 +53,6 @@ defmodule Nulla.HTTPSignature do {:ok, result} end - defp extract_public_key(%{"publicKey" => %{"publicKeyPem" => pem}}), do: {:ok, pem} - defp extract_public_key(_), do: {:error, :no_public_key} - defp verify_signature(public_key_pem, signed_string, signature_base64) do public_key = :public_key.pem_decode(public_key_pem) diff --git a/lib/nulla_web/controllers/inbox_controller.ex b/lib/nulla_web/controllers/inbox_controller.ex index 6bdecd3..052f7d7 100644 --- a/lib/nulla_web/controllers/inbox_controller.ex +++ b/lib/nulla_web/controllers/inbox_controller.ex @@ -19,14 +19,25 @@ defmodule NullaWeb.InboxController do with local_actor <- Actor.get_actor(ap_id: target_uri), {:ok, remote_actor_json} <- Utils.fetch_remote_actor(actor_uri), - :ok <- HTTPSignature.verify(conn, remote_actor_json), + :ok <- HTTPSignature.verify(conn, remote_actor_json["publicKey"]["publicKeyPem"]), {:ok, remote_actor} <- get_or_create_actor(remote_actor_json), {:ok, follow_activity} <- - create_follow_activity(follow_id, remote_actor.ap_id, target_uri), + Activity.create_activity(%{ + ap_id: follow_id, + type: "Follow", + actor: remote_actor.ap_id, + object: target_uri + }), {:ok, accept_activity} <- - create_accept_activity(accept_id, local_actor, follow_activity), + Activity.create_activity(%{ + id: accept_id, + ap_id: "https://#{local_actor.domain}/activities/accept/#{accept_id}", + type: "Accept", + actor: local_actor.ap_id, + object: Jason.encode!(follow_activity) + }), {:ok, _relation} <- get_or_create_relation(local_actor.id, remote_actor.id), - :ok <- deliver_accept(accept_activity, remote_actor_json, local_actor) do + :ok <- deliver_accept(accept_activity, remote_actor_json["inbox"], local_actor) do send_resp(conn, 200, "") else error -> @@ -56,25 +67,6 @@ defmodule NullaWeb.InboxController do end end - defp create_follow_activity(follow_id, actor_id, target_uri) do - Activity.create_activity(%{ - ap_id: follow_id, - type: "Follow", - actor: actor_id, - object: target_uri - }) - end - - defp create_accept_activity(accept_id, local_actor, follow_activity) do - Activity.create_activity(%{ - id: accept_id, - ap_id: "https://#{local_actor.domain}/activities/accept/#{accept_id}", - type: "Accept", - actor: local_actor.ap_id, - object: Jason.encode!(follow_activity) - }) - end - defp get_or_create_relation(local_actor_id, remote_actor_id) do case Relation.get_relation(local_actor_id: local_actor_id, remote_actor_id: remote_actor_id) do nil -> @@ -89,25 +81,15 @@ defmodule NullaWeb.InboxController do end end - defp deliver_accept(accept_activity, remote_actor_json, local_actor) do - inbox_url = remote_actor_json["inbox"] + defp deliver_accept(accept_activity, inbox_url, local_actor) do accept_activity = %Activity{accept_activity | object: Jason.decode!(accept_activity.object)} body = Jason.encode!(ActivityPub.activity(accept_activity)) - - digest = - :crypto.hash(:sha256, body) - |> Base.encode64() - |> then(&("SHA-256=" <> &1)) - - date = - DateTime.utc_now() - |> Calendar.strftime("%a, %d %b %Y %H:%M:%S GMT") - + digest = "SHA-256=" <> (:crypto.hash(:sha256, body) |> Base.encode64()) + date = DateTime.utc_now() |> Calendar.strftime("%a, %d %b %Y %H:%M:%S GMT") uri = URI.parse(inbox_url) - request_target = "post #{uri.path}" signature_string = """ - (request-target): #{request_target} + (request-target): post #{uri.path} host: #{uri.host} date: #{date} digest: #{digest} @@ -117,11 +99,8 @@ defmodule NullaWeb.InboxController do private_key = case :public_key.pem_decode(user.privateKeyPem) do - [{_type, der, _rest}] -> - :public_key.der_decode(:RSAPrivateKey, der) - - _ -> - raise "Invalid PEM format" + [entry] -> :public_key.pem_entry_decode(entry) + _ -> raise "Invalid PEM format" end signature = @@ -132,18 +111,17 @@ defmodule NullaWeb.InboxController do """ keyId="#{local_actor.publicKey["id"]}", algorithm="rsa-sha256", - headers="(request-target) host date digest content-type", + headers="(request-target) host date digest", signature="#{signature}" """ |> String.replace("\n", "") |> String.trim() headers = [ - {"host", uri.host}, - {"date", date}, - {"digest", digest}, - {"signature", signature_header}, - {"content-type", "application/activity+json"} + {"Content-Type", "application/activity+json"}, + {"Date", date}, + {"Digest", digest}, + {"Signature", signature_header} ] request = Finch.build(:post, inbox_url, headers, body) @@ -162,4 +140,41 @@ defmodule NullaWeb.InboxController do {:error, reason} end end + + def inbox(conn, %{ + "id" => accept_id, + "type" => "Accept", + "actor" => actor_uri, + "object" => object + }) do + with {:ok, remote_actor_json} <- Utils.fetch_remote_actor(actor_uri), + :ok <- HTTPSignature.verify(conn, remote_actor_json), + {:ok, remote_actor} <- get_or_create_actor(remote_actor_json), + {:ok, follow_activity} <- decode_follow_activity(object), + {:ok, local_actor} <- Actor.get_actor(ap_id: follow_activity["object"]), + {:ok, _relation} <- Relation.mark_follow_accepted(local_actor.id, remote_actor.id) do + send_resp(conn, 200, "") + else + error -> + IO.inspect(error, label: "Accept error") + json(conn, %{"error" => "Failed to process Accept"}) + end + end + + defp decode_follow_activity(object) when is_map(object), do: {:ok, object} + defp decode_follow_activity(object) when is_binary(object), do: Jason.decode(object) + + def inbox(conn, %{ + "id" => accept_id, + "type" => "Undo", + "actor" => actor_uri, + "object" => object + }) do + send_resp(conn, 200, "") + end + + def inbox(conn, params) do + IO.inspect(params) + send_resp(conn, 400, "") + end end