From 86bc35c2a741f6f7f71d9d00e373d2d9ca3ad441 Mon Sep 17 00:00:00 2001 From: Oneric Date: Sun, 9 Feb 2025 18:02:22 +0100 Subject: [PATCH] Actually check signature metadata --- config/config.exs | 7 +- lib/http_signatures/http_signatures.ex | 99 +++++- mix.exs | 1 + mix.lock | 13 + test/http_signatures_test.exs | 416 ++++++++++++++----------- 5 files changed, 348 insertions(+), 188 deletions(-) diff --git a/config/config.exs b/config/config.exs index d4210f3..0d239f0 100644 --- a/config/config.exs +++ b/config/config.exs @@ -6,5 +6,10 @@ import Config config :http_signatures, adapter: HTTPSignatures.NullAdapter if Mix.env() == :test do - config :http_signatures, adapter: HTTPSignatures.TestAdapter + config :http_signatures, + adapter: HTTPSignatures.TestAdapter, + # 20 years in seconds; to not just test our own signatures + # the test suite contains signatures from other real-world implementations + # which we cannot regenerate on the fly with prod-sane creation timestamps + max_sig_age: 631_152_000 end diff --git a/lib/http_signatures/http_signatures.ex b/lib/http_signatures/http_signatures.ex index 165f0e9..dddf96e 100644 --- a/lib/http_signatures/http_signatures.ex +++ b/lib/http_signatures/http_signatures.ex @@ -18,6 +18,9 @@ defmodule HTTPSignatures do @key_id "keyId" @request_target "(request-target)" + @max_clock_skew Application.compile_env(:http_signatures, :max_clock_skew, 2400) + @max_sig_age Application.compile_env(:http_signatures, :max_sig_age, 7200) + defp split_signature(sig) do default = %{"headers" => "date"} @@ -183,6 +186,85 @@ defmodule HTTPSignatures do end end + defp unixstr_to_time(unixstr) do + with {seconds, ""} when is_integer(seconds) <- Integer.parse(unixstr), + {:ok, time} <- DateTime.from_unix(seconds) do + {:ok, time} + else + _ -> {:error, :invalid_date} + end + end + + defp check_expiry(now, %{"(expires)" => expires_unix_seconds}) do + with {:ok, expires} <- unixstr_to_time(expires_unix_seconds), + true <- DateTime.diff(expires, now) > -@max_clock_skew do + :ok + else + false -> {:error, :too_old} + {:error, e} -> {:error, e} + end + end + + defp check_expiry(_, _), do: :ok + + defp check_created(now, %{"(created)" => created_unix_seconds}) do + with {:ok, created} <- unixstr_to_time(created_unix_seconds), + diff <- DateTime.diff(now, created), + {_, false} <- {:future, diff < -@max_clock_skew}, + {_, false} <- {:too_old, diff > @max_sig_age} do + :ok + else + {:future, _} -> {:error, :future_timestamp} + {:too_old, _} -> {:error, :too_old} + {:error, e} -> {:error, e} + end + end + + defp check_created(_, _), do: :ok + + defp check_date(now, %{"date" => date_str}) do + with {_, {:ok, created}} <- {:parse, Timex.parse(date_str, "{RFC1123}")}, + diff <- DateTime.diff(now, created), + {:future, false} <- {:future, diff < -@max_clock_skew}, + true <- diff < @max_sig_age do + :ok + else + {:parse, _} -> {:error, :invalid_date} + {:future, _} -> {:error, :future_timestamp} + _ -> {:error, :too_old} + end + end + + defp check_date(_, _), do: :ok + + defp check_timing(header_defs) do + now = DateTime.utc_now() + + with :ok <- check_date(now, header_defs), + :ok <- check_created(now, header_defs) do + check_expiry(now, header_defs) + else + # :error -> {:error, :too_old} + {:error, e} -> {:error, e} + end + end + + # Checks whether non-cryptographic parts are valid, + # e.g. not expired + defp check_contextual_validity(signature, header_defs) do + with {_, %{@key_id => kid}} when kid != "" <- {:key_id, signature}, + {_, :ok} <- {:timing, check_timing(header_defs)} do + :ok + else + {:key_id, sig} -> + Logger.debug("Signature is missing a valid key id!") + {:error, {:missing_key_id, sig}} + + {:timing, {:error, e}} -> + {:error, e} + end + end + @doc """ Validates the presence and authenticity of an incoming HTTP request @@ -196,8 +278,6 @@ defmodule HTTPSignatures do Note: results other than {:ok, key} and {:error, _} will only ever be returned if instructed to do so by the key adapter via {:halt, _}. - - XXX: currently the values for (expired) and (created) are not checked against the local system time """ @spec validate_conn(Plug.Conn.t(), [String.t() | (-> String.t() | [String.t()])], any()) :: {:ok, key :: HTTPSignatures.HTTPKey.t()} | {:error, any()} | any() @@ -207,24 +287,25 @@ defmodule HTTPSignatures do headers = Enum.into(conn.req_headers, %{}) with {_, sigstr} when sigstr != nil <- {:signature, headers["signature"]}, - {_, %{@key_id => kid} = signature} when kid != "" <- {:key_id, split_signature(sigstr)}, - {_, {:ok, %{} = pseudo_headers}} <- {:psuedo_headers, collect_pseudo_headers(signature)} do + signature <- split_signature(sigstr), + {_, {:ok, %{} = pseudo_headers}} <- {:psuedo_headers, collect_pseudo_headers(signature)}, + header_defs <- Map.merge(headers, pseudo_headers), + {_, :ok} <- {:context, check_contextual_validity(signature, header_defs)} do validate_signature_with_request_target_aliases( request_targets, signature, - Map.merge(headers, pseudo_headers), + header_defs, user_data ) else {:signature, _} -> {:error, :unsigned} - {:key_id, sig} -> - Logger.debug("Signature is missing a valid key id!") - {:error, {:missing_key_id, sig}} - {:pseudo_headers, {:error, _} = e} -> e + + {:context, {:error, _} = e} -> + e end end diff --git a/mix.exs b/mix.exs index bd00c48..df178df 100644 --- a/mix.exs +++ b/mix.exs @@ -28,6 +28,7 @@ defmodule HttpSignatures.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ + {:timex, "~> 3.7"}, {:credo, "~> 1.0", only: [:dev, :test], runtime: false}, {:ex_doc, "~> 0.19", only: :dev, runtime: false}, {:dialyxir, "~> 1.1.0", only: [:dev], runtime: false} diff --git a/mix.lock b/mix.lock index 2dc07a8..ef37ac0 100644 --- a/mix.lock +++ b/mix.lock @@ -1,14 +1,27 @@ %{ "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, + "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, + "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, "dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"}, "earmark_parser": {:hex, :earmark_parser, "1.4.42", "f23d856f41919f17cd06a493923a722d87a2d684f143a1e663c04a2b93100682", [:mix], [], "hexpm", "6915b6ca369b5f7346636a2f41c6a6d78b5af419d61a611079189233358b8b8b"}, "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "ex_doc": {:hex, :ex_doc, "0.36.1", "4197d034f93e0b89ec79fac56e226107824adcce8d2dd0a26f5ed3a95efc36b1", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "d7d26a7cf965dacadcd48f9fa7b5953d7d0cfa3b44fa7a65514427da44eafd89"}, + "expo": {:hex, :expo, "1.1.0", "f7b9ed7fb5745ebe1eeedf3d6f29226c5dd52897ac67c0f8af62a07e661e5c75", [:mix], [], "hexpm", "fbadf93f4700fb44c331362177bdca9eeb8097e8b0ef525c9cc501cb9917c960"}, "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, + "gettext": {:hex, :gettext, "0.26.2", "5978aa7b21fada6deabf1f6341ddba50bc69c999e812211903b169799208f2a8", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "aa978504bcf76511efdc22d580ba08e2279caab1066b76bb9aa81c4a1e0a32a5"}, + "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, + "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, "makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, + "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.1", "f41275a0354c736db4b1d255b5d2a27c91028e55c21ea3145b938e22649ffa3f", [:mix], [], "hexpm", "605e44204998f138d6e13be366c8e81af860e726c8177caf50067e1b618fe522"}, + "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, + "timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"}, + "tzdata": {:hex, :tzdata, "1.1.2", "45e5f1fcf8729525ec27c65e163be5b3d247ab1702581a94674e008413eef50b", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "cec7b286e608371602318c414f344941d5eb0375e14cfdab605cca2fe66cba8b"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, } diff --git a/test/http_signatures_test.exs b/test/http_signatures_test.exs index e0bdbfb..0e05e9b 100644 --- a/test/http_signatures_test.exs +++ b/test/http_signatures_test.exs @@ -29,6 +29,10 @@ defmodule HttpSignaturesTest do "expires" => "1388967500" } + # since the lib (currently) doesn't handle digest verification or generation, + # anything works and we only need to distinguish bodyless from body requests + @stub_body %{"type" => "Note", "content" => "Hi!"} + @default_signature """ keyId="Test",algorithm="rsa-sha256",signature="jKyvPcxB4JbmYY4mByyBY7cZfNl4OW9HpFQlG7N4YcJPteKTu4MWCLyk+gIr0wDgqtLWf9NLpMAMimdfsH7FSWGfbMFSrsVTHNTk0rK3usrfFnti1dxsM4jl0kYJCKTGI/UWkqiaxwNiKqGcdlEDrTcUhhsFsOIo8VhddmZTZ8w=" """ @@ -45,13 +49,14 @@ defmodule HttpSignaturesTest do keyId="Test",algorithm="rsa-sha256",headers="(request-target) host date content-type digest content-length",signature="Ef7MlxLXoBovhil3AlyjtBwAL9g4TN3tibLj7uuNB3CROat/9KaeQ4hW2NiJ+pZ6HQEOx9vYZAyi+7cmIkmJszJCut5kQLAwuX+Ms/mUFvpKlSo9StS2bMXDBNjOh4Auj774GFj4gwjS+3NhFeoqyr/MuN6HsEnkvn6zdgfE2i0=" """ - defp signed_conn(signature), do: %{req_headers: [{"signature", signature} | @headers]} + defp signed_conn(signature, headers, body_params \\ @stub_body), + do: %{ + req_headers: [{"signature", signature} | headers], + body_params: body_params + } - defp assert_signature(signature, request_targets \\ [@pseudo_headers["request-target"]]) do - assert match?( - {:ok, %HTTPKey{user_data: %{"key_id" => @key_id}}}, - HTTPSignatures.validate_conn(signed_conn(signature), request_targets) - ) + defp check_signature(signature, request_targets \\ [@pseudo_headers["request-target"]]) do + HTTPSignatures.validate_conn(signed_conn(signature, @headers, %{}), request_targets) end defp assert_conn(conn, request_targets, key_id) do @@ -61,55 +66,64 @@ defmodule HttpSignaturesTest do ) end - test "validates the default case" do - assert_signature(@default_signature, [nil]) + test "accepts the default case" do + assert match?( + {:ok, %HTTPKey{user_data: %{"key_id" => @key_id}}}, + check_signature(@default_signature, [nil]) + ) end - test "validates the basic case" do - assert_signature(@basic_signature) + test "accepts signature without digest if body-less" do + assert match?( + {:ok, %HTTPKey{user_data: %{"key_id" => @key_id}}}, + check_signature(@basic_signature) + ) end - test "validates the timestamped case" do - assert_signature(@timestamped_signature) + test "rejects signature if expired" do + assert {:error, :too_old} == check_signature(@timestamped_signature) end test "validates the all-headers case" do - assert_signature(@all_headers_signature) + assert match?( + {:ok, %HTTPKey{user_data: %{"key_id" => @key_id}}}, + check_signature(@all_headers_signature) + ) end test "it validates a conn" do - conn = %{ - req_headers: [ - {"host", "localtesting.pleroma.lol"}, - {"connection", "close"}, - {"content-length", "2316"}, - {"user-agent", "http.rb/2.2.2 (Mastodon/2.1.0.rc3; +http://mastodon.example.org/)"}, - {"date", "Sun, 10 Dec 2017 14:23:49 GMT"}, - {"digest", "SHA-256=x/bHADMW8qRrq2NdPb5P9fl0lYpKXXpe5h5maCIL0nM="}, - {"content-type", "application/activity+json"}, - {"signature", - "keyId=\"http://example.org/mystery/key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"i0FQvr51sj9BoWAKydySUAO1RDxZmNY6g7M62IA7VesbRSdFZZj9/fZapLp6YSuvxUF0h80ZcBEq9GzUDY3Chi9lx6yjpUAS2eKb+Am/hY3aswhnAfYd6FmIdEHzsMrpdKIRqO+rpQ2tR05LwiGEHJPGS0p528NvyVxrxMT5H5yZS5RnxY5X2HmTKEgKYYcvujdv7JWvsfH88xeRS7Jlq5aDZkmXvqoR4wFyfgnwJMPLel8P/BUbn8BcXglH/cunR0LUP7sflTxEz+Rv5qg+9yB8zgBsB4C0233WpcJxjeD6Dkq0EcoJObBR56F8dcb7NQtUDu7x6xxzcgSd7dHm5w==\""} - ] - } + conn = + signed_conn( + "keyId=\"http://example.org/mystery/key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"i0FQvr51sj9BoWAKydySUAO1RDxZmNY6g7M62IA7VesbRSdFZZj9/fZapLp6YSuvxUF0h80ZcBEq9GzUDY3Chi9lx6yjpUAS2eKb+Am/hY3aswhnAfYd6FmIdEHzsMrpdKIRqO+rpQ2tR05LwiGEHJPGS0p528NvyVxrxMT5H5yZS5RnxY5X2HmTKEgKYYcvujdv7JWvsfH88xeRS7Jlq5aDZkmXvqoR4wFyfgnwJMPLel8P/BUbn8BcXglH/cunR0LUP7sflTxEz+Rv5qg+9yB8zgBsB4C0233WpcJxjeD6Dkq0EcoJObBR56F8dcb7NQtUDu7x6xxzcgSd7dHm5w==\"", + [ + {"host", "localtesting.pleroma.lol"}, + {"connection", "close"}, + {"content-length", "2316"}, + {"user-agent", "http.rb/2.2.2 (Mastodon/2.1.0.rc3; +http://mastodon.example.org/)"}, + {"date", "Sun, 10 Dec 2017 14:23:49 GMT"}, + {"digest", "SHA-256=x/bHADMW8qRrq2NdPb5P9fl0lYpKXXpe5h5maCIL0nM="}, + {"content-type", "application/activity+json"} + ] + ) assert_conn(conn, ["post /users/demiurge/inbox"], "http://example.org/mystery/key") end test "it validates a conn and fetches the key" do - conn = %{ - req_headers: [ - {"host", "localtesting.pleroma.lol"}, - {"x-forwarded-for", "127.0.0.1"}, - {"connection", "close"}, - {"content-length", "2307"}, - {"user-agent", "http.rb/2.2.2 (Mastodon/2.1.0.rc3; +http://mastodon.example.org/)"}, - {"date", "Sun, 11 Feb 2018 17:12:01 GMT"}, - {"digest", "SHA-256=UXsAnMtR9c7mi1FOf6HRMtPgGI1yi2e9nqB/j4rZ99I="}, - {"content-type", "application/activity+json"}, - {"signature", - "keyId=\"http://mastodon.example.org/users/admin#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"qXKqpQXUpC3d9bZi2ioEeAqP8nRMD021CzH1h6/w+LRk4Hj31ARJHDwQM+QwHltwaLDUepshMfz2WHSXAoLmzWtvv7xRwY+mRqe+NGk1GhxVZ/LSrO/Vp7rYfDpfdVtkn36LU7/Bzwxvvaa4ZWYltbFsRBL0oUrqsfmJFswNCQIG01BB52BAhGSCORHKtQyzo1IZHdxl8y80pzp/+FOK2SmHkqWkP9QbaU1qTZzckL01+7M5btMW48xs9zurEqC2sM5gdWMQSZyL6isTV5tmkTZrY8gUFPBJQZgihK44v3qgfWojYaOwM8ATpiv7NG8wKN/IX7clDLRMA8xqKRCOKw==\""} - ] - } + conn = + signed_conn( + "keyId=\"http://mastodon.example.org/users/admin#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"qXKqpQXUpC3d9bZi2ioEeAqP8nRMD021CzH1h6/w+LRk4Hj31ARJHDwQM+QwHltwaLDUepshMfz2WHSXAoLmzWtvv7xRwY+mRqe+NGk1GhxVZ/LSrO/Vp7rYfDpfdVtkn36LU7/Bzwxvvaa4ZWYltbFsRBL0oUrqsfmJFswNCQIG01BB52BAhGSCORHKtQyzo1IZHdxl8y80pzp/+FOK2SmHkqWkP9QbaU1qTZzckL01+7M5btMW48xs9zurEqC2sM5gdWMQSZyL6isTV5tmkTZrY8gUFPBJQZgihK44v3qgfWojYaOwM8ATpiv7NG8wKN/IX7clDLRMA8xqKRCOKw==\"", + [ + {"host", "localtesting.pleroma.lol"}, + {"x-forwarded-for", "127.0.0.1"}, + {"connection", "close"}, + {"content-length", "2307"}, + {"user-agent", "http.rb/2.2.2 (Mastodon/2.1.0.rc3; +http://mastodon.example.org/)"}, + {"date", "Sun, 11 Feb 2018 17:12:01 GMT"}, + {"digest", "SHA-256=UXsAnMtR9c7mi1FOf6HRMtPgGI1yi2e9nqB/j4rZ99I="}, + {"content-type", "application/activity+json"} + ] + ) assert_conn( conn, @@ -119,58 +133,58 @@ defmodule HttpSignaturesTest do end test "validate this" do - conn = %{ - req_headers: [ - {"x-forwarded-for", "149.202.73.191"}, - {"host", "testing.pleroma.lol"}, - {"x-cluster-client-ip", "149.202.73.191"}, - {"connection", "upgrade"}, - {"content-length", "2396"}, - {"user-agent", "http.rb/3.0.0 (Mastodon/2.2.0; +https://niu.moe/)"}, - {"date", "Sun, 18 Feb 2018 20:31:51 GMT"}, - {"digest", "SHA-256=dzH+vLyhxxALoe9RJdMl4hbEV9bGAZnSfddHQzeidTU="}, - {"content-type", "application/activity+json"}, - {"signature", - "keyId=\"https://niu.moe/users/rye#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"wtxDg4kIpW7nsnUcVJhBk6SgJeDZOocr8yjsnpDRqE52lR47SH6X7G16r7L1AUJdlnbfx7oqcvomoIJoHB3ghP6kRnZW6MyTMZ2jPoi3g0iC5RDqv6oAmDSO14iw6U+cqZbb3P/odS5LkbThF0UNXcfenVNfsKosIJycFjhNQc54IPCDXYq/7SArEKJp8XwEgzmiC2MdxlkVIUSTQYfjM4EG533cwlZocw1mw72e5mm/owTa80BUZAr0OOuhoWARJV9btMb02ZyAF6SCSoGPTA37wHyfM1Dk88NHf7Z0Aov/Fl65dpRM+XyoxdkpkrhDfH9qAx4iuV2VEWddQDiXHA==\""} - ] - } + conn = + signed_conn( + "keyId=\"https://niu.moe/users/rye#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"wtxDg4kIpW7nsnUcVJhBk6SgJeDZOocr8yjsnpDRqE52lR47SH6X7G16r7L1AUJdlnbfx7oqcvomoIJoHB3ghP6kRnZW6MyTMZ2jPoi3g0iC5RDqv6oAmDSO14iw6U+cqZbb3P/odS5LkbThF0UNXcfenVNfsKosIJycFjhNQc54IPCDXYq/7SArEKJp8XwEgzmiC2MdxlkVIUSTQYfjM4EG533cwlZocw1mw72e5mm/owTa80BUZAr0OOuhoWARJV9btMb02ZyAF6SCSoGPTA37wHyfM1Dk88NHf7Z0Aov/Fl65dpRM+XyoxdkpkrhDfH9qAx4iuV2VEWddQDiXHA==\"", + [ + {"x-forwarded-for", "149.202.73.191"}, + {"host", "testing.pleroma.lol"}, + {"x-cluster-client-ip", "149.202.73.191"}, + {"connection", "upgrade"}, + {"content-length", "2396"}, + {"user-agent", "http.rb/3.0.0 (Mastodon/2.2.0; +https://niu.moe/)"}, + {"date", "Sun, 18 Feb 2018 20:31:51 GMT"}, + {"digest", "SHA-256=dzH+vLyhxxALoe9RJdMl4hbEV9bGAZnSfddHQzeidTU="}, + {"content-type", "application/activity+json"} + ] + ) assert_conn(conn, ["post /inbox"], "https://niu.moe/users/rye#main-key") end test "validate this too" do - conn = %{ - req_headers: [ - {"x-forwarded-for", "149.202.73.191"}, - {"host", "testing.pleroma.lol"}, - {"x-cluster-client-ip", "149.202.73.191"}, - {"connection", "upgrade"}, - {"content-length", "2342"}, - {"user-agent", "http.rb/3.0.0 (Mastodon/2.2.0; +https://niu.moe/)"}, - {"date", "Sun, 18 Feb 2018 21:44:46 GMT"}, - {"digest", "SHA-256=vS8uDOJlyAu78cF3k5EzrvaU9iilHCX3chP37gs5sS8="}, - {"content-type", "application/activity+json"}, - {"signature", - "keyId=\"https://niu.moe/users/rye#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"IN6fHD8pLiDEf35dOaRHzJKc1wBYh3/Yq0ItaNGxUSbJTd2xMjigZbcsVKzvgYYjglDDN+disGNeD+OBKwMqkXWaWe/lyMc9wHvCH5NMhpn/A7qGLY8yToSt4vh8ytSkZKO6B97yC+Nvy6Fz/yMbvKtFycIvSXCq417cMmY6f/aG+rtMUlTbKO5gXzC7SUgGJCtBPCh1xZzu5/w0pdqdjO46ePNeR6JyJSLLV4hfo3+p2n7SRraxM4ePVCUZqhwS9LPt3Zdhy3ut+IXCZgMVIZggQFM+zXLtcXY5HgFCsFQr5WQDu+YkhWciNWtKFnWfAsnsg5sC330lZ/0Z8Z91yA==\""} - ] - } + conn = + signed_conn( + "keyId=\"https://niu.moe/users/rye#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"IN6fHD8pLiDEf35dOaRHzJKc1wBYh3/Yq0ItaNGxUSbJTd2xMjigZbcsVKzvgYYjglDDN+disGNeD+OBKwMqkXWaWe/lyMc9wHvCH5NMhpn/A7qGLY8yToSt4vh8ytSkZKO6B97yC+Nvy6Fz/yMbvKtFycIvSXCq417cMmY6f/aG+rtMUlTbKO5gXzC7SUgGJCtBPCh1xZzu5/w0pdqdjO46ePNeR6JyJSLLV4hfo3+p2n7SRraxM4ePVCUZqhwS9LPt3Zdhy3ut+IXCZgMVIZggQFM+zXLtcXY5HgFCsFQr5WQDu+YkhWciNWtKFnWfAsnsg5sC330lZ/0Z8Z91yA==\"", + [ + {"x-forwarded-for", "149.202.73.191"}, + {"host", "testing.pleroma.lol"}, + {"x-cluster-client-ip", "149.202.73.191"}, + {"connection", "upgrade"}, + {"content-length", "2342"}, + {"user-agent", "http.rb/3.0.0 (Mastodon/2.2.0; +https://niu.moe/)"}, + {"date", "Sun, 18 Feb 2018 21:44:46 GMT"}, + {"digest", "SHA-256=vS8uDOJlyAu78cF3k5EzrvaU9iilHCX3chP37gs5sS8="}, + {"content-type", "application/activity+json"} + ] + ) assert_conn(conn, ["post /inbox"], "https://niu.moe/users/rye#main-key") end test "it validates a bridgy signature" do - conn = %{ - req_headers: [ - {"user-agent", "Bridgy Fed (https://fed.brid.gy/)"}, - {"date", "Mon, 15 Jul 2024 04:35:16 GMT"}, - {"host", "akko.wtf"}, - {"content-type", - "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""}, - {"digest", "SHA-256=aD6VWn+4CUs5hBeEjAiSdRjga/h5Qnub0gLVSqSosSk="}, - {"signature", - ~s|keyId="https://fed.brid.gy/snarfed.org#key",algorithm="rsa-sha256",signature="pezDShRpsN8sErE5xOPWh4iJOzi2nz7IcwxtCYEQ3KfqezkQIGFb9lxCwgUZDUdueX6R/eUXGJ5S5GdzjEMbs90iIzaqvN2fRmhs3VHgd7Y1O8bMJGfkOz5i6JllyPMgfFT9937147ATmGTmo7h4NhE5XgQ6VwNijNuBhLGOZsCLYqldpuyGuHXyLOmGHsIL/tBf0LBKPMyE6S70hEmh6Zx7nQeW5Kt9CTZIxnSbeFrg2HMiMaFRM5gWtPTH5N6Jr6SGal/vXT9VrN1wjwZI83y45aHmpnU7VImSWGWyXxcveHvspXtDciWAPNU8ng6aglYVgWrLaQz70ApRb10fXg==",headers="date host digest (request-target)"|} - ] - } + conn = + signed_conn( + ~s|keyId="https://fed.brid.gy/snarfed.org#key",algorithm="rsa-sha256",signature="pezDShRpsN8sErE5xOPWh4iJOzi2nz7IcwxtCYEQ3KfqezkQIGFb9lxCwgUZDUdueX6R/eUXGJ5S5GdzjEMbs90iIzaqvN2fRmhs3VHgd7Y1O8bMJGfkOz5i6JllyPMgfFT9937147ATmGTmo7h4NhE5XgQ6VwNijNuBhLGOZsCLYqldpuyGuHXyLOmGHsIL/tBf0LBKPMyE6S70hEmh6Zx7nQeW5Kt9CTZIxnSbeFrg2HMiMaFRM5gWtPTH5N6Jr6SGal/vXT9VrN1wjwZI83y45aHmpnU7VImSWGWyXxcveHvspXtDciWAPNU8ng6aglYVgWrLaQz70ApRb10fXg==",headers="date host digest (request-target)"|, + [ + {"user-agent", "Bridgy Fed (https://fed.brid.gy/)"}, + {"date", "Mon, 15 Jul 2024 04:35:16 GMT"}, + {"host", "akko.wtf"}, + {"content-type", + "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""}, + {"digest", "SHA-256=aD6VWn+4CUs5hBeEjAiSdRjga/h5Qnub0gLVSqSosSk="} + ] + ) assert_conn(conn, ["post /users/Oneric/inbox"], "https://fed.brid.gy/snarfed.org#key") end @@ -181,28 +195,31 @@ defmodule HttpSignaturesTest do "http://mastodon.example.org/users/admin#main-key", %{ :host => "mastodon.example.org", - "(request-target)" => "post http.//example.com/inbox" + "(request-target)" => "post http://example.com/inbox", + "(created)" => "#{DateTime.utc_now() |> DateTime.to_unix()}", + "digest" => + "SHA256-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" } ) =~ "keyId=\"" end test "this too" do - conn = %{ - req_headers: [ - {"host", "soc.canned-death.us"}, - {"user-agent", "http.rb/3.0.0 (Mastodon/2.2.0; +https://mst3k.interlinked.me/)"}, - {"date", "Sun, 11 Mar 2018 12:19:36 GMT"}, - {"digest", "SHA-256=V7Hl6qDK2m8WzNsjzNYSBISi9VoIXLFlyjF/a5o1SOc="}, - {"content-type", "application/activity+json"}, - {"signature", - "keyId=\"https://mst3k.interlinked.me/users/luciferMysticus#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"CTYdK5a6lYMxzmqjLOpvRRASoxo2Rqib2VrAvbR5HaTn80kiImj15pCpAyx8IZp53s0Fn/y8MjCTzp+absw8kxx0k2sQAXYs2iy6xhdDUe7iGzz+XLAEqLyZIZfecynaU2nb3Z2XnFDjhGjR1vj/JP7wiXpwp6o1dpDZj+KT2vxHtXuB9585V+sOHLwSB1cGDbAgTy0jx/2az2EGIKK2zkw1KJuAZm0DDMSZalp/30P8dl3qz7DV2EHdDNfaVtrs5BfbDOZ7t1hCcASllzAzgVGFl0BsrkzBfRMeUMRucr111ZG+c0BNOEtJYOHSyZsSSdNknElggCJekONYMYk5ZA==\""}, - {"x-forwarded-for", "2607:5300:203:2899::31:1337"}, - {"x-forwarded-host", "soc.canned-death.us"}, - {"x-forwarded-server", "soc.canned-death.us"}, - {"connection", "Keep-Alive"}, - {"content-length", "2006"} - ] - } + conn = + signed_conn( + "keyId=\"https://mst3k.interlinked.me/users/luciferMysticus#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"CTYdK5a6lYMxzmqjLOpvRRASoxo2Rqib2VrAvbR5HaTn80kiImj15pCpAyx8IZp53s0Fn/y8MjCTzp+absw8kxx0k2sQAXYs2iy6xhdDUe7iGzz+XLAEqLyZIZfecynaU2nb3Z2XnFDjhGjR1vj/JP7wiXpwp6o1dpDZj+KT2vxHtXuB9585V+sOHLwSB1cGDbAgTy0jx/2az2EGIKK2zkw1KJuAZm0DDMSZalp/30P8dl3qz7DV2EHdDNfaVtrs5BfbDOZ7t1hCcASllzAzgVGFl0BsrkzBfRMeUMRucr111ZG+c0BNOEtJYOHSyZsSSdNknElggCJekONYMYk5ZA==\"", + [ + {"host", "soc.canned-death.us"}, + {"user-agent", "http.rb/3.0.0 (Mastodon/2.2.0; +https://mst3k.interlinked.me/)"}, + {"date", "Sun, 11 Mar 2018 12:19:36 GMT"}, + {"digest", "SHA-256=V7Hl6qDK2m8WzNsjzNYSBISi9VoIXLFlyjF/a5o1SOc="}, + {"content-type", "application/activity+json"}, + {"x-forwarded-for", "2607:5300:203:2899::31:1337"}, + {"x-forwarded-host", "soc.canned-death.us"}, + {"x-forwarded-server", "soc.canned-death.us"}, + {"connection", "Keep-Alive"}, + {"content-length", "2006"} + ] + ) assert_conn( conn, @@ -212,22 +229,22 @@ defmodule HttpSignaturesTest do end test "works with request_aliases" do - conn = %{ - req_headers: [ - {"host", "soc.canned-death.us"}, - {"user-agent", "http.rb/3.0.0 (Mastodon/2.2.0; +https://mst3k.interlinked.me/)"}, - {"date", "Sun, 11 Mar 2018 12:19:36 GMT"}, - {"digest", "SHA-256=V7Hl6qDK2m8WzNsjzNYSBISi9VoIXLFlyjF/a5o1SOc="}, - {"content-type", "application/activity+json"}, - {"signature", - "keyId=\"https://mst3k.interlinked.me/users/luciferMysticus#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"CTYdK5a6lYMxzmqjLOpvRRASoxo2Rqib2VrAvbR5HaTn80kiImj15pCpAyx8IZp53s0Fn/y8MjCTzp+absw8kxx0k2sQAXYs2iy6xhdDUe7iGzz+XLAEqLyZIZfecynaU2nb3Z2XnFDjhGjR1vj/JP7wiXpwp6o1dpDZj+KT2vxHtXuB9585V+sOHLwSB1cGDbAgTy0jx/2az2EGIKK2zkw1KJuAZm0DDMSZalp/30P8dl3qz7DV2EHdDNfaVtrs5BfbDOZ7t1hCcASllzAzgVGFl0BsrkzBfRMeUMRucr111ZG+c0BNOEtJYOHSyZsSSdNknElggCJekONYMYk5ZA==\""}, - {"x-forwarded-for", "2607:5300:203:2899::31:1337"}, - {"x-forwarded-host", "soc.canned-death.us"}, - {"x-forwarded-server", "soc.canned-death.us"}, - {"connection", "Keep-Alive"}, - {"content-length", "2006"} - ] - } + conn = + signed_conn( + "keyId=\"https://mst3k.interlinked.me/users/luciferMysticus#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"CTYdK5a6lYMxzmqjLOpvRRASoxo2Rqib2VrAvbR5HaTn80kiImj15pCpAyx8IZp53s0Fn/y8MjCTzp+absw8kxx0k2sQAXYs2iy6xhdDUe7iGzz+XLAEqLyZIZfecynaU2nb3Z2XnFDjhGjR1vj/JP7wiXpwp6o1dpDZj+KT2vxHtXuB9585V+sOHLwSB1cGDbAgTy0jx/2az2EGIKK2zkw1KJuAZm0DDMSZalp/30P8dl3qz7DV2EHdDNfaVtrs5BfbDOZ7t1hCcASllzAzgVGFl0BsrkzBfRMeUMRucr111ZG+c0BNOEtJYOHSyZsSSdNknElggCJekONYMYk5ZA==\"", + [ + {"host", "soc.canned-death.us"}, + {"user-agent", "http.rb/3.0.0 (Mastodon/2.2.0; +https://mst3k.interlinked.me/)"}, + {"date", "Sun, 11 Mar 2018 12:19:36 GMT"}, + {"digest", "SHA-256=V7Hl6qDK2m8WzNsjzNYSBISi9VoIXLFlyjF/a5o1SOc="}, + {"content-type", "application/activity+json"}, + {"x-forwarded-for", "2607:5300:203:2899::31:1337"}, + {"x-forwarded-host", "soc.canned-death.us"}, + {"x-forwarded-server", "soc.canned-death.us"}, + {"connection", "Keep-Alive"}, + {"content-length", "2006"} + ] + ) assert_conn( conn, @@ -237,22 +254,22 @@ defmodule HttpSignaturesTest do end test "works with lazy-eval request_aliases" do - conn = %{ - req_headers: [ - {"host", "soc.canned-death.us"}, - {"user-agent", "http.rb/3.0.0 (Mastodon/2.2.0; +https://mst3k.interlinked.me/)"}, - {"date", "Sun, 11 Mar 2018 12:19:36 GMT"}, - {"digest", "SHA-256=V7Hl6qDK2m8WzNsjzNYSBISi9VoIXLFlyjF/a5o1SOc="}, - {"content-type", "application/activity+json"}, - {"signature", - "keyId=\"https://mst3k.interlinked.me/users/luciferMysticus#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"CTYdK5a6lYMxzmqjLOpvRRASoxo2Rqib2VrAvbR5HaTn80kiImj15pCpAyx8IZp53s0Fn/y8MjCTzp+absw8kxx0k2sQAXYs2iy6xhdDUe7iGzz+XLAEqLyZIZfecynaU2nb3Z2XnFDjhGjR1vj/JP7wiXpwp6o1dpDZj+KT2vxHtXuB9585V+sOHLwSB1cGDbAgTy0jx/2az2EGIKK2zkw1KJuAZm0DDMSZalp/30P8dl3qz7DV2EHdDNfaVtrs5BfbDOZ7t1hCcASllzAzgVGFl0BsrkzBfRMeUMRucr111ZG+c0BNOEtJYOHSyZsSSdNknElggCJekONYMYk5ZA==\""}, - {"x-forwarded-for", "2607:5300:203:2899::31:1337"}, - {"x-forwarded-host", "soc.canned-death.us"}, - {"x-forwarded-server", "soc.canned-death.us"}, - {"connection", "Keep-Alive"}, - {"content-length", "2006"} - ] - } + conn = + signed_conn( + "keyId=\"https://mst3k.interlinked.me/users/luciferMysticus#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"CTYdK5a6lYMxzmqjLOpvRRASoxo2Rqib2VrAvbR5HaTn80kiImj15pCpAyx8IZp53s0Fn/y8MjCTzp+absw8kxx0k2sQAXYs2iy6xhdDUe7iGzz+XLAEqLyZIZfecynaU2nb3Z2XnFDjhGjR1vj/JP7wiXpwp6o1dpDZj+KT2vxHtXuB9585V+sOHLwSB1cGDbAgTy0jx/2az2EGIKK2zkw1KJuAZm0DDMSZalp/30P8dl3qz7DV2EHdDNfaVtrs5BfbDOZ7t1hCcASllzAzgVGFl0BsrkzBfRMeUMRucr111ZG+c0BNOEtJYOHSyZsSSdNknElggCJekONYMYk5ZA==\"", + [ + {"host", "soc.canned-death.us"}, + {"user-agent", "http.rb/3.0.0 (Mastodon/2.2.0; +https://mst3k.interlinked.me/)"}, + {"date", "Sun, 11 Mar 2018 12:19:36 GMT"}, + {"digest", "SHA-256=V7Hl6qDK2m8WzNsjzNYSBISi9VoIXLFlyjF/a5o1SOc="}, + {"content-type", "application/activity+json"}, + {"x-forwarded-for", "2607:5300:203:2899::31:1337"}, + {"x-forwarded-host", "soc.canned-death.us"}, + {"x-forwarded-server", "soc.canned-death.us"}, + {"connection", "Keep-Alive"}, + {"content-length", "2006"} + ] + ) assert_conn( conn, @@ -262,22 +279,22 @@ defmodule HttpSignaturesTest do end test "works with lazy-eval batch request_aliases" do - conn = %{ - req_headers: [ - {"host", "soc.canned-death.us"}, - {"user-agent", "http.rb/3.0.0 (Mastodon/2.2.0; +https://mst3k.interlinked.me/)"}, - {"date", "Sun, 11 Mar 2018 12:19:36 GMT"}, - {"digest", "SHA-256=V7Hl6qDK2m8WzNsjzNYSBISi9VoIXLFlyjF/a5o1SOc="}, - {"content-type", "application/activity+json"}, - {"signature", - "keyId=\"https://mst3k.interlinked.me/users/luciferMysticus#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"CTYdK5a6lYMxzmqjLOpvRRASoxo2Rqib2VrAvbR5HaTn80kiImj15pCpAyx8IZp53s0Fn/y8MjCTzp+absw8kxx0k2sQAXYs2iy6xhdDUe7iGzz+XLAEqLyZIZfecynaU2nb3Z2XnFDjhGjR1vj/JP7wiXpwp6o1dpDZj+KT2vxHtXuB9585V+sOHLwSB1cGDbAgTy0jx/2az2EGIKK2zkw1KJuAZm0DDMSZalp/30P8dl3qz7DV2EHdDNfaVtrs5BfbDOZ7t1hCcASllzAzgVGFl0BsrkzBfRMeUMRucr111ZG+c0BNOEtJYOHSyZsSSdNknElggCJekONYMYk5ZA==\""}, - {"x-forwarded-for", "2607:5300:203:2899::31:1337"}, - {"x-forwarded-host", "soc.canned-death.us"}, - {"x-forwarded-server", "soc.canned-death.us"}, - {"connection", "Keep-Alive"}, - {"content-length", "2006"} - ] - } + conn = + signed_conn( + "keyId=\"https://mst3k.interlinked.me/users/luciferMysticus#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"CTYdK5a6lYMxzmqjLOpvRRASoxo2Rqib2VrAvbR5HaTn80kiImj15pCpAyx8IZp53s0Fn/y8MjCTzp+absw8kxx0k2sQAXYs2iy6xhdDUe7iGzz+XLAEqLyZIZfecynaU2nb3Z2XnFDjhGjR1vj/JP7wiXpwp6o1dpDZj+KT2vxHtXuB9585V+sOHLwSB1cGDbAgTy0jx/2az2EGIKK2zkw1KJuAZm0DDMSZalp/30P8dl3qz7DV2EHdDNfaVtrs5BfbDOZ7t1hCcASllzAzgVGFl0BsrkzBfRMeUMRucr111ZG+c0BNOEtJYOHSyZsSSdNknElggCJekONYMYk5ZA==\"", + [ + {"host", "soc.canned-death.us"}, + {"user-agent", "http.rb/3.0.0 (Mastodon/2.2.0; +https://mst3k.interlinked.me/)"}, + {"date", "Sun, 11 Mar 2018 12:19:36 GMT"}, + {"digest", "SHA-256=V7Hl6qDK2m8WzNsjzNYSBISi9VoIXLFlyjF/a5o1SOc="}, + {"content-type", "application/activity+json"}, + {"x-forwarded-for", "2607:5300:203:2899::31:1337"}, + {"x-forwarded-host", "soc.canned-death.us"}, + {"x-forwarded-server", "soc.canned-death.us"}, + {"connection", "Keep-Alive"}, + {"content-length", "2006"} + ] + ) assert_conn( conn, @@ -287,46 +304,84 @@ defmodule HttpSignaturesTest do end test "returns a custom result from adapter on key fetch" do - conn = %{ - req_headers: [ - {"host", "soc.canned-death.us"}, - {"date", "Sun, 11 Mar 2018 12:19:36 GMT"}, - {"digest", "SHA-256=V7Hl6qDK2m8WzNsjzNYSBISi9VoIXLFlyjF/a5o1SOc="}, - {"content-type", "application/activity+json"}, - {"signature", - "keyId=\"halt_id\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest -content-type\",signature=\"CTYdK5a6lYMxzmqjLOpvRRASoxo2Rqib2VrAvbR5HaTn80kiImj15pCpAyx8IZp53s0Fn/y8MjCTzp+absw8kxx0k2sQAXYs2iy6xhdDUe7iGzz+XLAEqLyZIZfecynaU2nb3Z2XnFDjhGjR1vj/JP7wiXpwp6o1dpDZj+KT2vxHtXuB9585V+sOHLwSB1cGDbAgTy0jx/2az2EGIKK2zkw1KJuAZm0DDMSZalp/30P8dl3qz7DV2EHdDNfaVtrs5BfbDOZ7t1hCcASllzAzgVGFl0BsrkzBfRMeUMRucr111ZG+c0BNOEtJYOHSyZsSSdNknElggCJekONYMYk5ZA==\""} - ] - } + conn = + signed_conn( + "keyId=\"halt_id\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"CTYdK5a6lYMxzmqjLOpvRRASoxo2Rqib2VrAvbR5HaTn80kiImj15pCpAyx8IZp53s0Fn/y8MjCTzp+absw8kxx0k2sQAXYs2iy6xhdDUe7iGzz+XLAEqLyZIZfecynaU2nb3Z2XnFDjhGjR1vj/JP7wiXpwp6o1dpDZj+KT2vxHtXuB9585V+sOHLwSB1cGDbAgTy0jx/2az2EGIKK2zkw1KJuAZm0DDMSZalp/30P8dl3qz7DV2EHdDNfaVtrs5BfbDOZ7t1hCcASllzAzgVGFl0BsrkzBfRMeUMRucr111ZG+c0BNOEtJYOHSyZsSSdNknElggCJekONYMYk5ZA==\"", + [ + {"host", "soc.canned-death.us"}, + {"date", "Sun, 11 Mar 2018 12:19:36 GMT"}, + {"digest", "SHA-256=V7Hl6qDK2m8WzNsjzNYSBISi9VoIXLFlyjF/a5o1SOc="}, + {"content-type", "application/activity+json"} + ] + ) assert HTTPSignatures.validate_conn(conn, ["post /inbox"]) == :custom_res end test "returns a custom result from adapter on key refetch" do - conn = %{ - req_headers: [ - {"host", "soc.canned-death.us"}, - {"date", "Sun, 11 Mar 2018 12:19:36 GMT"}, - {"digest", "SHA-256=V7Hl6qDK2m8WzNsjzNYSBISi9VoIXLFlyjF/a5o1SOc="}, - {"content-type", "application/activity+json"}, - {"signature", - "keyId=\"halt_refetch_id\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"CTYdK5a6lYMxzmqjLOpvRRASoxo2Rqib2VrAvbR5HaTn80kiImj15pCpAyx8IZp53s0Fn/y8MjCTzp+absw8kxx0k2sQAXYs2iy6xhdDUe7iGzz+XLAEqLyZIZfecynaU2nb3Z2XnFDjhGjR1vj/JP7wiXpwp6o1dpDZj+KT2vxHtXuB9585V+sOHLwSB1cGDbAgTy0jx/2az2EGIKK2zkw1KJuAZm0DDMSZalp/30P8dl3qz7DV2EHdDNfaVtrs5BfbDOZ7t1hCcASllzAzgVGFl0BsrkzBfRMeUMRucr111ZG+c0BNOEtJYOHSyZsSSdNknElggCJekONYMYk5ZA==\""} - ] - } + conn = + signed_conn( + "keyId=\"halt_refetch_id\",algorithm=\"rsa-sha256\",headers=\"(request-target) user-agent host date digest content-type\",signature=\"CTYdK5a6lYMxzmqjLOpvRRASoxo2Rqib2VrAvbR5HaTn80kiImj15pCpAyx8IZp53s0Fn/y8MjCTzp+absw8kxx0k2sQAXYs2iy6xhdDUe7iGzz+XLAEqLyZIZfecynaU2nb3Z2XnFDjhGjR1vj/JP7wiXpwp6o1dpDZj+KT2vxHtXuB9585V+sOHLwSB1cGDbAgTy0jx/2az2EGIKK2zkw1KJuAZm0DDMSZalp/30P8dl3qz7DV2EHdDNfaVtrs5BfbDOZ7t1hCcASllzAzgVGFl0BsrkzBfRMeUMRucr111ZG+c0BNOEtJYOHSyZsSSdNknElggCJekONYMYk5ZA==\"", + [ + {"host", "soc.canned-death.us"}, + {"date", "Sun, 11 Mar 2018 12:19:36 GMT"}, + {"digest", "SHA-256=V7Hl6qDK2m8WzNsjzNYSBISi9VoIXLFlyjF/a5o1SOc="}, + {"content-type", "application/activity+json"} + ] + ) assert HTTPSignatures.validate_conn(conn, ["post /inbox"]) == :custom_res2 end - test "returns an error on unknown key" do - conn = %{ - req_headers: [ - {"date", "Sun, 11 Mar 2018 12:19:36 GMT"}, - {"digest", "SHA-256=V7Hl6qDK2m8WzNsjzNYSBISi9VoIXLFlyjF/a5o1SOc="}, - {"signature", - "keyId=\"unknown_id\",algorithm=\"rsa-sha256\",headers=\"(request-target) date digest\",signature=\"CTYdK5a6lYMxzmqjLOpvRRASoxo2Rqib2VrAvbR5HaTn80kiImj15pCpAyx8IZp53s0Fn/y8MjCTzp+absw8kxx0k2sQAXYs2iy6xhdDUe7iGzz+XLAEqLyZIZfecynaU2nb3Z2XnFDjhGjR1vj/JP7wiXpwp6o1dpDZj+KT2vxHtXuB9585V+sOHLwSB1cGDbAgTy0jx/2az2EGIKK2zkw1KJuAZm0DDMSZalp/30P8dl3qz7DV2EHdDNfaVtrs5BfbDOZ7t1hCcASllzAzgVGFl0BsrkzBfRMeUMRucr111ZG+c0BNOEtJYOHSyZsSSdNknElggCJekONYMYk5ZA==\""} - ] + test "accepts its own signature" do + request_target = "post http://example.com/inbox" + + conn_headers = %{ + "digest" => "SHA256-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "host" => "example.com" } + signature = + HTTPSignatures.sign( + %HTTPKey{key: @private_key}, + "Test", + %{ + "(request-target)" => request_target, + "(created)" => DateTime.utc_now() |> DateTime.to_unix(), + :host => conn_headers["host"], + "digest" => conn_headers["digest"] + } + ) + + conn = signed_conn(signature, Enum.into(conn_headers, [])) + + assert match?({:ok, _}, HTTPSignatures.validate_conn(conn, [request_target])) + end + + test "rejects too old signatures" do + conn = + signed_conn( + "keyId=\"Test\",algorithm=\"rsa-sha256\",headers=\"(request-target) date digest\",signature=\"fTmDCQYXsib05+Np0R3jQhG5KYu8v8chjiZnpZxKMnTPLJ6C/XDeqTIAvBtikuIrSWNG0k3/xSMXR3c1IVw/T5lwo9KAjsZU39ztYGBckE8X9BdT1GklpyWDtI4X9MvBrq5lmMn/r1gbQFzelQEF1+RdK6kbDwg8goAYGEkm9KE=\"", + [ + {"date", "Wed, 09 Feb 2000 00:00:00 GMT"}, + {"digest", "SHA256-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"} + ] + ) + + assert {:error, :too_old} == + HTTPSignatures.validate_conn(conn, ["post http://example.com/inbox"]) + end + + test "returns an error on unknown key" do + conn = + signed_conn( + "keyId=\"unknown_id\",algorithm=\"rsa-sha256\",headers=\"(request-target) date digest\",signature=\"CTYdK5a6lYMxzmqjLOpvRRASoxo2Rqib2VrAvbR5HaTn80kiImj15pCpAyx8IZp53s0Fn/y8MjCTzp+absw8kxx0k2sQAXYs2iy6xhdDUe7iGzz+XLAEqLyZIZfecynaU2nb3Z2XnFDjhGjR1vj/JP7wiXpwp6o1dpDZj+KT2vxHtXuB9585V+sOHLwSB1cGDbAgTy0jx/2az2EGIKK2zkw1KJuAZm0DDMSZalp/30P8dl3qz7DV2EHdDNfaVtrs5BfbDOZ7t1hCcASllzAzgVGFl0BsrkzBfRMeUMRucr111ZG+c0BNOEtJYOHSyZsSSdNknElggCJekONYMYk5ZA==\"", + [ + {"date", "Sun, 11 Mar 2018 12:19:36 GMT"}, + {"digest", "SHA-256=V7Hl6qDK2m8WzNsjzNYSBISi9VoIXLFlyjF/a5o1SOc="} + ] + ) + assert match?( {:error, {:fetch_key, _}}, HTTPSignatures.validate_conn(conn, ["post /inbox"]) @@ -351,10 +406,15 @@ content-type\",signature=\"CTYdK5a6lYMxzmqjLOpvRRASoxo2Rqib2VrAvbR5HaTn80kiImj15 private_key = %HTTPKey{key: private_key} - headers = %{host: "test.test", "content-length": 100} + headers = %{ + "host" => "test.test", + "content-length" => "0", + "date" => "Sat, 01 Feb 2025 00:00:00 GMT", + "(request-target)" => "get https://example.com/object/123" + } expected_signature = - ~s|keyId="https://mastodon.social/users/lambadalambda#main-key",algorithm="rsa-sha256",headers="content-length host",signature="sibUOoqsFfTDerquAkyprxzDjmJm6erYc42W5w1IyyxusWngSinq5ILTjaBxFvfarvc7ci1xAi+5gkBwtshRMWm7S+Uqix24Yg5EYafXRun9P25XVnYBEIH4XQ+wlnnzNIXQkU3PU9e6D8aajDZVp3hPJNeYt1gIPOA81bROI8/glzb1SAwQVGRbqUHHHKcwR8keiR/W2h7BwG3pVRy4JgnIZRSW7fQogKedDg02gzRXwUDFDk0pr2p3q6bUWHUXNV8cZIzlMK+v9NlyFbVYBTHctAR26GIAN6Hz0eV0mAQAePHDY1mXppbA8Gpp6hqaMuYfwifcXmcc+QFm4e+n3A=="| + ~s|keyId="https://mastodon.social/users/lambadalambda#main-key",algorithm="rsa-sha256",headers="(request-target) content-length date host",signature="b3ORqxn55+DOz4LljaugGfV3lOnozvW1fO54N6RE1Zr9o4wdaVmV/8CsaN3FCMKDf2rWbr6qWDyC0s6zbhwBGQOZc2SdchFxc4OFGZtEILNqCC4EYQkJ85xwR7DkVxDCZNBJRc6HKuAGOd4TavmWgoUaiyj+6aOho7X/lAFhhx6rNDZtOI7/3D/j/6t5d3vz90dyhiYSg0jPg+rLnFq99oZ+LUVBAHFVFBumZSoZeF6AoxnHtoeG98Qf0ICYPfdCfOIfbMjw6xQt2ciichcagDcsZS6sO3/wBAznloQIg/pO4BhV1G2Gnp/SVjL9CsaXndXGW8ABbQIDELI+IOQjSg=="| signature = HTTPSignatures.sign(private_key, user_ap_id, headers)