Don't validate on missing public key.
[akkoma] / lib / pleroma / web / http_signatures / http_signatures.ex
1 # https://tools.ietf.org/html/draft-cavage-http-signatures-08
2 defmodule Pleroma.Web.HTTPSignatures do
3 alias Pleroma.User
4 alias Pleroma.Web.ActivityPub.ActivityPub
5 require Logger
6
7 def split_signature(sig) do
8 default = %{"headers" => "date"}
9
10 sig =
11 sig
12 |> String.trim()
13 |> String.split(",")
14 |> Enum.reduce(default, fn part, acc ->
15 [key | rest] = String.split(part, "=")
16 value = Enum.join(rest, "=")
17 Map.put(acc, key, String.trim(value, "\""))
18 end)
19
20 Map.put(sig, "headers", String.split(sig["headers"], ~r/\s/))
21 end
22
23 def validate(headers, signature, public_key) do
24 sigstring = build_signing_string(headers, signature["headers"])
25 Logger.debug("Signature: #{signature["signature"]}")
26 Logger.debug("Sigstring: #{sigstring}")
27 {:ok, sig} = Base.decode64(signature["signature"])
28 :public_key.verify(sigstring, :sha256, sig, public_key)
29 end
30
31 def validate_conn(conn) do
32 # TODO: How to get the right key and see if it is actually valid for that request.
33 # For now, fetch the key for the actor.
34 with actor_id <- conn.params["actor"],
35 {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
36 if validate_conn(conn, public_key) do
37 true
38 else
39 Logger.debug("Could not validate, re-fetching user and trying one more time.")
40 # Fetch user anew and try one more time
41 with actor_id <- conn.params["actor"],
42 {:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id),
43 {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
44 validate_conn(conn, public_key)
45 end
46 end
47 else
48 e ->
49 Logger.debug("Could not public key!")
50 false
51 end
52 end
53
54 def validate_conn(conn, public_key) do
55 headers = Enum.into(conn.req_headers, %{})
56 signature = split_signature(headers["signature"])
57 validate(headers, signature, public_key)
58 end
59
60 def build_signing_string(headers, used_headers) do
61 used_headers
62 |> Enum.map(fn header -> "#{header}: #{headers[header]}" end)
63 |> Enum.join("\n")
64 end
65
66 def sign(user, headers) do
67 with {:ok, %{info: %{"keys" => keys}}} <- Pleroma.Web.WebFinger.ensure_keys_present(user),
68 {:ok, private_key, _} = Pleroma.Web.Salmon.keys_from_pem(keys) do
69 sigstring = build_signing_string(headers, Map.keys(headers))
70
71 signature =
72 :public_key.sign(sigstring, :sha256, private_key)
73 |> Base.encode64()
74
75 [
76 keyId: user.ap_id <> "#main-key",
77 algorithm: "rsa-sha256",
78 headers: Map.keys(headers) |> Enum.join(" "),
79 signature: signature
80 ]
81 |> Enum.map(fn {k, v} -> "#{k}=\"#{v}\"" end)
82 |> Enum.join(",")
83 end
84 end
85 end