From fa0f47d8e1571de475b6c3df3618e6b72dc784dd Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 7 Dec 2018 10:47:31 +0100 Subject: [PATCH] Add digest, date and request-target in HTTP signature --- lib/service/activity_pub/activity_pub.ex | 23 +++++++++++++++---- .../http_signatures/http_signatures.ex | 18 +++++++++++++++ mix.exs | 2 +- .../service/activitypub/activitypub_test.exs | 18 +++++++++++++++ 4 files changed, 55 insertions(+), 6 deletions(-) diff --git a/lib/service/activity_pub/activity_pub.ex b/lib/service/activity_pub/activity_pub.ex index 7c07452d8..ee44e1d43 100644 --- a/lib/service/activity_pub/activity_pub.ex +++ b/lib/service/activity_pub/activity_pub.ex @@ -15,6 +15,7 @@ defmodule Mobilizon.Service.ActivityPub do alias Mobilizon.Actors.Actor alias Mobilizon.Service.Federator + alias Mobilizon.Service.HTTPSignatures require Logger import Mobilizon.Service.ActivityPub.Utils @@ -277,23 +278,35 @@ defmodule Mobilizon.Service.ActivityPub do def publish_one(%{inbox: inbox, json: json, actor: actor, id: id}) do Logger.info("Federating #{id} to #{inbox}") - host = URI.parse(inbox).host + {host, path} = URI.parse(inbox) + + digest = HTTPSignatures.build_digest(json) + date = HTTPSignatures.generate_date_header() + request_target = HTTPSignatures.generate_request_target("POST", path) signature = - Mobilizon.Service.HTTPSignatures.sign(actor, %{ + HTTPSignatures.sign(actor, %{ host: host, - "content-length": byte_size(json) + "content-length": byte_size(json), + "(request-target)": request_target, + digest: digest, + date: date }) HTTPoison.post( inbox, json, - [{"Content-Type", "application/activity+json"}, {"signature", signature}], + [ + {"Content-Type", "application/activity+json"}, + {"signature", signature}, + {"digest", digest}, + {"date", date} + ], hackney: [pool: :default] ) end - # Fetching a remote actor's informations through it's AP ID + # Fetching a remote actor's informations through it's AP ID @spec fetch_and_prepare_actor_from_url(String.t()) :: {:ok, struct()} | {:error, atom()} | any() defp fetch_and_prepare_actor_from_url(url) do Logger.debug("Fetching and preparing actor from url") diff --git a/lib/service/http_signatures/http_signatures.ex b/lib/service/http_signatures/http_signatures.ex index ef4aae7cf..9c1a320fc 100644 --- a/lib/service/http_signatures/http_signatures.ex +++ b/lib/service/http_signatures/http_signatures.ex @@ -94,6 +94,24 @@ defmodule Mobilizon.Service.HTTPSignatures do err -> Logger.error("Unable to sign headers") Logger.error(inspect(err)) + nil end end + + def generate_date_header(date \\ Timex.now("GMT")) do + with {:ok, date} <- Timex.format(date, "%a, %d %b %Y %H:%M:%S %Z", :strftime) do + date + else + {:error, err} -> + Logger.error("Unable to generate date header") + Logger.error(inspect(err)) + nil + end + end + + def generate_request_target(method, path), do: "#{method} #{path}" + + def build_digest(body) do + "SHA-256=" <> (:crypto.hash(:sha256, body) |> Base.encode64()) + end end diff --git a/mix.exs b/mix.exs index 8725cf8d8..30476dbb7 100644 --- a/mix.exs +++ b/mix.exs @@ -35,7 +35,7 @@ defmodule Mobilizon.Mixfile do def application do [ mod: {Mobilizon.Application, []}, - extra_applications: [:logger, :runtime_tools, :guardian, :bamboo, :geolix] + extra_applications: [:logger, :runtime_tools, :guardian, :bamboo, :geolix, :crypto] ] end diff --git a/test/mobilizon/service/activitypub/activitypub_test.exs b/test/mobilizon/service/activitypub/activitypub_test.exs index 714bed538..d9c004102 100644 --- a/test/mobilizon/service/activitypub/activitypub_test.exs +++ b/test/mobilizon/service/activitypub/activitypub_test.exs @@ -6,6 +6,7 @@ defmodule Mobilizon.Service.Activitypub.ActivitypubTest do alias Mobilizon.Events alias Mobilizon.Actors.Actor alias Mobilizon.Actors + alias Mobilizon.Service.HTTPSignatures alias Mobilizon.Service.ActivityPub use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney @@ -13,6 +14,23 @@ defmodule Mobilizon.Service.Activitypub.ActivitypubTest do HTTPoison.start() end + describe "setting HTTP signature" do + test "set http signature header" do + actor = insert(:actor) + + signature = + HTTPSignatures.sign(actor, %{ + host: "example.com", + "content-length": 15, + digest: Jason.encode!(%{id: "my_id"}) |> HTTPSignatures.build_digest(), + "(request-target)": HTTPSignatures.generate_request_target("POST", "/inbox"), + date: HTTPSignatures.generate_date_header() + }) + + assert signature =~ "headers=\"(request-target) content-length date digest host\"" + end + end + describe "fetching actor from it's url" do test "returns an actor from nickname" do use_cassette "activity_pub/fetch_tcit@framapiaf.org" do